HOW TO:對 Web 使用者顯示當地語系化的日期和時間資訊
更新:2007 年 11 月
由於網頁可在任何地方顯示,因此與使用者互動時,剖析和格式化日期與時間值的作業不應倚賴預設格式 (大部分情況下為 Web 伺服器上本機文化特性的格式)。處理使用者輸入之日期和時間字串的 Web Form 應改用使用者偏好的文化特性剖析字串。同樣地,對使用者顯示的日期和時間資料應使用符合使用者文化特性的格式。本主題將說明如何執行這項作業。
剖析使用者輸入的日期和時間字串
判斷 HttpRequest.UserLanguages 屬性傳回的字串陣列是否已填入。如果尚未填入,則繼續進行步驟 6。
如果 UserLanguages 屬性所傳回的字串陣列已填入,就會擷取它的第一個項目。第一個項目指出使用者的預設或慣用的語言和區域。
呼叫 CultureInfo.CultureInfo(String, Boolean) 建構函式以具現化 CultureInfo 物件,此物件代表使用者慣用的文化特性。
呼叫 DateTime 或 DateTimeOffset 型別的 TryParse 或 Parse 方法嘗試轉換。使用 TryParse 或 Parse 方法的多載與 provider 參數,並將下列任一項傳遞給該多載:
在步驟 3 中建立的 CultureInfo 物件。
在步驟 3 中所建立 CultureInfo 物件的 DateTimeFormat 屬性傳回的 DateTimeFormatInfo 物件。
如果轉換失敗,則針對 UserLanguages 屬性傳回的字串陣列中其餘每一個項目重複步驟 2 到 4。
如果轉換還是失敗,或者,如果 UserLanguages 屬性所傳回的字串陣列為空白,請使用由 CultureInfo.InvariantCulture 屬性所傳回的 Invariant 文化特性 (不因文化特定而異) 來剖析字串。
剖析使用者要求的本地日期和時間
在 Web Form 中加入 HiddenField 控制項。
建立 JavaScript 函式,該函式會將目前的日期和時間以及本機時區與 Coordinated Universal Time (UTC) 之間的位移寫入 Value 屬性,藉此處理 Submit 按鈕的 onClick 事件。使用分隔符號 (例如分號) 分隔字串的兩個元件。
將指令碼的文字傳遞至 ClientScriptManager.RegisterClientScriptBlock(Type, String, String, Boolean) 方法,藉此使用 Web Form 的 PreRender 事件將函式插入 HTML 輸出資料流中。
藉由提供 JavaScript 函式的名稱給 Submit 按鈕的 OnClientClick 屬性,連接事件處理常式與 Submit 按鈕的 onClick 事件。
為 Submit 按鈕的 Click 事件建立處理常式。
判斷 HttpRequest.UserLanguages 屬性傳回的字串陣列是否已填入事件處理常式中。如果尚未填入,則繼續進行步驟 14。
如果 UserLanguages 屬性所傳回的字串陣列已填入,就會擷取它的第一個項目。第一個項目指出使用者的預設或慣用的語言和區域。
呼叫 CultureInfo.CultureInfo(String, Boolean) 建構函式以具現化 CultureInfo 物件,此物件代表使用者慣用的文化特性。
將指派給 Value 屬性的字串傳遞至 Split 方法,藉此將使用者本地日期和時間的字串表示,以及使用者本機時區位移的字串表示儲存到不同的陣列項目中。
呼叫 DateTime.Parse 或 DateTime.TryParse(String, IFormatProvider, DateTimeStyles, DateTime%) 方法,將使用者要求的日期和時間轉換成 DateTime 值。使用方法多載與 provider 參數,並將下列任一項傳遞給該多載:
在步驟 8 中建立的 CultureInfo 物件。
在步驟 8 中所建立 CultureInfo 物件的 DateTimeFormat 屬性傳回的 DateTimeFormatInfo 物件。
如果步驟 10 中的剖析作業失敗,請移至步驟 13。否則請呼叫 UInt32.Parse(String) 方法,將使用者時區位移的字串表示轉換成整數。
具現化 DateTimeOffset,它會藉由呼叫 DateTimeOffset.DateTimeOffset(DateTime, TimeSpan) 建構函式的方式呈現使用者的本機時間。
如果步驟 10 中的轉換失敗,則針對 UserLanguages 屬性傳回的字串陣列中其餘每一個項目重複步驟 7 到 12。
如果轉換還是失敗,或者,如果 UserLanguages 屬性所傳回的字串陣列為空白,請使用由 CultureInfo.InvariantCulture 屬性所傳回的 Invariant 文化特性 (不因文化特定而異) 來剖析字串。然後重複步驟 7 到 12。
執行結果為 DateTimeOffset 物件,表示網頁使用者的本地時間。然後您可以呼叫 ToUniversalTime 方法以判斷對等的 UTC。您也可以呼叫 TimeZoneInfo.ConvertTime(DateTimeOffset, TimeZoneInfo) 方法並且傳遞 TimeZoneInfo.Local 的值做為轉換時間的目標時區,藉此判斷 Web 伺服器上的對等日期和時間。
範例
以下範例同時包含 HTML 原始檔和 ASP.NET Web Form 的程式碼,會要求使用者輸入日期和時間值。用戶端指令碼也會將使用者要求的本地日期和時間資訊,以及使用者時區與 UTC 之間的位移寫入隱藏欄位中。伺服器會接著剖析這項資訊,並傳回顯示使用者輸入的網頁。此外還會利用使用者的本地時間、伺服器上的時間及 UTC,顯示使用者要求的日期和時間。
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Globalization" %>
<%@ Assembly Name="System.Core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script >
Protected Sub OKButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles OKButton.Click
Dim locale As String = ""
Dim styles As DateTimeStyles = DateTimeStyles.AllowInnerWhite Or DateTimeStyles.AllowLeadingWhite Or _
DateTimeStyles.AllowTrailingWhite
Dim inputDate, localDate As Date
Dim localDateOffset As DateTimeOffset
Dim integerOffset As Integer
Dim result As Boolean
' Exit if input is absent.
If String.IsNullOrEmpty(Me.DateString.Text) Then Exit Sub
' Hide form elements.
Me.DateForm.Visible = False
' Create array of CultureInfo objects
Dim cultures(Request.UserLanguages.Length) As CultureInfo
For ctr As Integer = Request.UserLanguages.GetLowerBound(0) To Request.UserLanguages.GetUpperBound(0)
locale = Request.UserLanguages(ctr)
If Not String.IsNullOrEmpty(locale) Then
' Remove quality specifier, if present.
If locale.Contains(";") Then _
locale = Left(locale, InStr(locale, ";") - 1)
Try
cultures(ctr) = New CultureInfo(Request.UserLanguages(ctr), False)
Catch
End Try
Else
cultures(ctr) = CultureInfo.CurrentCulture
End If
Next
cultures(Request.UserLanguages.Length) = CultureInfo.InvariantCulture
' Parse input using each culture.
For Each culture As CultureInfo In cultures
result = Date.TryParse(Me.DateString.Text, culture.DateTimeFormat, styles, inputDate)
If result Then Exit For
Next
' Display result to user.
If result Then
Response.Write("<P />")
Response.Write("The date you input was " + Server.HtmlEncode(CStr(Me.DateString.Text)) + "<BR />")
Else
' Unhide form.
Me.DateForm.Visible = True
Response.Write("<P />")
Response.Write("Unable to recognize " + Server.HtmlEncode(Me.DateString.Text) + ".<BR />")
End If
' Get date and time information from hidden field.
Dim dates() As String = Request.Form.Item("DateInfo").Split(";")
' Parse local date using each culture.
For Each culture As CultureInfo In cultures
result = Date.TryParse(dates(0), culture.DateTimeFormat, styles, localDate)
If result Then Exit For
Next
' Parse offset
result = Integer.TryParse(dates(1), integerOffset)
' Instantiate DateTimeOffset object representing user's local time
If result Then
Try
localDateOffset = New DateTimeOffset(localDate, New TimeSpan(0, -integerOffset, 0))
Catch ex As Exception
result = False
End Try
End If
' Display result to user.
If result Then
Response.Write("<P />")
Response.Write("Your local date and time is " + localDateOffset.ToString() + ".<BR />")
Response.Write("The date and time on the server is " & _
TimeZoneInfo.ConvertTime(localDateOffset, _
TimeZoneInfo.Local).ToString() & ".<BR />")
Response.Write("Coordinated Universal Time is " + localDateOffset.ToUniversalTime.ToString() + ".<BR />")
Else
Response.Write("<P />")
Response.Write("Unable to recognize " + Server.HtmlEncode(dates(0)) & ".<BR />")
End If
End Sub
Protected Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender
Dim script As String = "function AddDateInformation() { " & vbCrLf & _
"var today = new Date();" & vbCrLf & _
"document.DateForm.DateInfo.value = today.toLocaleString() + " & Chr(34) & Chr(59) & Chr(34) & " + today.getTimezoneOffset();" & vbCrLf & _
" }"
' Register client script
Dim scriptMgr As ClientScriptManager = Page.ClientScript
scriptMgr.RegisterClientScriptBlock(Me.GetType(), "SubmitOnClick", script, True)
End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" >
<title>Parsing a Date and Time Value</title>
</head>
<body>
<form id="DateForm" >
<div>
<center>
<asp:Label ID="Label1" Text="Enter a Date and Time:" Width="248px"></asp:Label>
<asp:TextBox ID="DateString" Width="176px"></asp:TextBox><br />
</center>
<br />
<center>
<asp:Button ID="OKButton" Text="Button"
OnClientClick="AddDateInformation()" onclick="OKButton_Click" />
<asp:HiddenField ID="DateInfo" Value="" />
</center>
<br />
</div>
</form>
</body>
</html>
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Globalization" %>
<%@ Assembly Name="System.Core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script >
protected void OKButton_Click(object sender, EventArgs e)
{
string locale = "";
DateTimeStyles styles = DateTimeStyles.AllowInnerWhite | DateTimeStyles.AllowLeadingWhite |
DateTimeStyles.AllowTrailingWhite;
DateTime inputDate;
DateTime localDate = DateTime.Now;
DateTimeOffset localDateOffset = DateTimeOffset.Now;
int integerOffset;
bool result = false;
// Exit if input is absent.
if (string.IsNullOrEmpty(this.DateString.Text)) return;
// Hide form elements.
this.DateForm.Visible = false;
// Create array of CultureInfo objects
CultureInfo[] cultures = new CultureInfo[Request.UserLanguages.Length + 1];
for (int ctr = Request.UserLanguages.GetLowerBound(0); ctr <= Request.UserLanguages.GetUpperBound(0);
ctr++)
{
locale = Request.UserLanguages[ctr];
if (! string.IsNullOrEmpty(locale))
{
// Remove quality specifier, if present.
if (locale.Contains(";"))
locale = locale.Substring(locale.IndexOf(';') -1);
try
{
cultures[ctr] = new CultureInfo(Request.UserLanguages[ctr], false);
}
catch (Exception) { }
}
else
{
cultures[ctr] = CultureInfo.CurrentCulture;
}
}
cultures[Request.UserLanguages.Length] = CultureInfo.InvariantCulture;
// Parse input using each culture.
foreach (CultureInfo culture in cultures)
{
result = DateTime.TryParse(this.DateString.Text, culture.DateTimeFormat, styles, out inputDate);
if (result) break;
}
// Display result to user.
if (result)
{
Response.Write("<P />");
Response.Write("The date you input was " + Server.HtmlEncode(this.DateString.Text) + "<BR />");
}
else
{
// Unhide form.
this.DateForm.Visible = true;
Response.Write("<P />");
Response.Write("Unable to recognize " + Server.HtmlEncode(this.DateString.Text) + ".<BR />");
}
// Get date and time information from hidden field.
string[] dates= Request.Form["DateInfo"].Split(';');
// Parse local date using each culture.
foreach (CultureInfo culture in cultures)
{
result = DateTime.TryParse(dates[0], culture.DateTimeFormat, styles, out localDate);
if (result) break;
}
// Parse offset
result = int.TryParse(dates[1], out integerOffset);
// Instantiate DateTimeOffset object representing user's local time
if (result)
{
try
{
localDateOffset = new DateTimeOffset(localDate, new TimeSpan(0, -integerOffset, 0));
}
catch (Exception)
{
result = false;
}
}
// Display result to user.
if (result)
{
Response.Write("<P />");
Response.Write("Your local date and time is " + localDateOffset.ToString() + ".<BR />");
Response.Write("The date and time on the server is " +
TimeZoneInfo.ConvertTime(localDateOffset,
TimeZoneInfo.Local).ToString() + ".<BR />");
Response.Write("Coordinated Universal Time is " + localDateOffset.ToUniversalTime().ToString() + ".<BR />");
}
else
{
Response.Write("<P />");
Response.Write("Unable to recognize " + Server.HtmlEncode(dates[0]) + ".<BR />");
}
}
protected void Page_PreRender(object sender, System.EventArgs e)
{
string script = "function AddDateInformation() { \n" +
"var today = new Date();\n" +
"document.DateForm.DateInfo.value = today.toLocaleString() + \";\" + today.getTimezoneOffset();\n" +
" }";
// Register client script
ClientScriptManager scriptMgr = Page.ClientScript;
scriptMgr.RegisterClientScriptBlock(this.GetType(), "SubmitOnClick", script, true);
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" >
<title>Parsing a Date and Time Value</title>
</head>
<body>
<form id="DateForm" >
<div>
<center>
<asp:Label ID="Label1" Text="Enter a Date and Time:" Width="248px"></asp:Label>
<asp:TextBox ID="DateString" Width="176px"></asp:TextBox><br />
</center>
<br />
<center>
<asp:Button ID="OKButton" Text="Button"
OnClientClick="AddDateInformation()" onclick="OKButton_Click" />
<asp:HiddenField ID="DateInfo" Value="" />
</center>
<br />
</div>
</form>
</body>
</html>
用戶端指令碼會呼叫 JavaScript toLocaleString 方法。如此會產生一個字串,該字串符合使用者地區設定的格式設定慣例,這可增加在伺服器上剖析該字串的成功率。
HttpRequest.UserLanguages 屬性會從 HTTP 要求所包含 Accept-Language 標頭中的文化特性名稱填入。不過,並非所有瀏覽器的要求都包含 Accept-Language 標頭,而且使用者可以完全隱藏標頭。因此,剖析使用者輸入時務必有後援文化特性。通常後援文化特性是 CultureInfo.InvariantCulture 傳回的不因國別而異的文化特性。使用者也可以將文字方塊中輸入的文化特性名稱提供給 Internet Explorer,如此就會有文化特性名稱無效的可能。因此具現化 CultureInfo 物件時,務必使用例外處理。
從 Internet Explorer 提交的 HTTP 要求擷取時,HttpRequest.UserLanguages 陣列會依使用者偏好設定填入。陣列中的第一個項目包含使用者主要文化特性/區域的名稱。如果陣列包含任何額外的項目,Internet Explorer 會為這些項目指派任意限定規範,並且使用分號與文化特性名稱分隔。例如,fr-FR 文化特性的項目可能採用這個格式 fr-FR;q=0.7。
範例會呼叫 CultureInfo 建構函式,且其 useUserOverride 參數會設為 false,以建立新的 CultureInfo 物件。如此可確定,如果文化特性名稱是伺服器上預設的文化特性名稱,則類別建構函式建立的 CultureInfo 物件會包含文化特性的預設設定,並且不會反映使用伺服器的 [地區及語言選項] 應用程式覆寫的任何設定。伺服器上任何覆寫設定的值都不會出現在使用者的系統上,或是反映在使用者輸入中。
由於這個範例會剖析兩個日期和時間的字串表示 (一個由使用者輸入,另一個儲存到隱藏欄位),因此會預先定義可能需要的 CultureInfo 物件。它會建立 CultureInfo 物件陣列,該陣列會比 HttpRequest.UserLanguages 屬性傳回的項目數大。然後具現化每個語言/區域字串的 CultureInfo 物件,同時還會具現化代表 CultureInfo.InvariantCulture 的 CultureInfo 物件。
您的程式碼可以呼叫 Parse 或 TryParse 方法,將使用者的日期和時間字串表示轉換成 DateTime 值。您可能需要針對單一剖析作業重複呼叫剖析方法。因此 TryParse 方法可能較佳,因為它會在剖析作業失敗時傳回 false。相反地,對於 Web 應用程式而言,處理 Parse 方法可能擲回的重複例外狀況可能是相當昂貴的方法。
編譯程式碼
若要編譯程式碼,請建立沒有程式碼後置 (Code-behind) 的 ASP.NET 網頁。然後將範例複製到網頁中,取代所有現有的程式碼。ASP.NET 網頁應包含下列控制項:
名為 DateString 的 TextBox 控制項。
名為 DateInfo 的 HiddenField 控制項。
安全性
為避免使用者將指令碼插入 HTML 資料流中,絕不可將使用者輸入直接回應 (Echo) 至伺服器回應中,而是使用 HttpServerUtility.HtmlEncode 方法將使用者輸入編碼。