如何:向 Web 用户显示本地化的日期和时间信息
更新:2007 年 11 月
由于网页可以在世界上的任何位置显示,因此在与用户交互时,不应依赖默认格式(通常为反映 Web 服务器本地区域性的格式)对日期和时间值执行分析和格式化操作。正确的做法是,Web 窗体应使用用户首选的区域性来处理用户输入的日期和时间字符串。同样,向用户显示的日期和时间数据的格式应符合用户的区域性。本主题演示如何实现此目的。
分析用户输入的日期和时间字符串
确定由 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 属性返回的固定区域性分析字符串。
分析用户请求的本地日期和时间
向 Web 窗体中添加一个 HiddenField 控件。
通过向 Value 属性中写入当前日期和时间以及本地时区与协调世界时 (UTC) 之间的偏移量,创建一个用来处理 Submit 按钮的 onClick 事件的 JavaScript 函数。请使用分隔符(例如分号)分隔字符串的两个组成部分。
通过向 ClientScriptManager.RegisterClientScriptBlock(Type, String, String, Boolean) 方法传递脚本文本,使用 Web 窗体的 PreRender 事件向 HTML 输出流中注入该函数。
通过向 Submit 按钮的 OnClientClick 属性提供该 JavaScript 函数的名称,将事件处理程序连接到 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(DateTime, TimeSpan) 构造函数实例化表示用户本地时间的 DateTimeOffset。
如果步骤 10 中的转换失败,请为由 UserLanguages 属性返回的字符串数组中剩余的每个元素重复步骤 7 到步骤 12。
如果转换仍然失败,或由 UserLanguages 属性返回的字符串数组为空,请使用由 CultureInfo.InvariantCulture 属性返回的固定区域性分析字符串。然后重复步骤 7 到步骤 12。
结果是一个表示网页用户本地时间的 DateTimeOffset 对象。接下来,可以通过调用 ToUniversalTime 方法来确定等效的 UTC。此外,还可以通过调用 TimeZoneInfo.ConvertTime(DateTimeOffset, TimeZoneInfo) 方法并传递 TimeZoneInfo.Local 的值(作为要将时间转换到的时区)来确定 Web 服务器上等效的日期和时间。
示例
下面的示例既包含 HTML 源代码,也包含让用户输入日期和时间值的 ASP.NET Web 窗体的代码。另外,还有一个客户端脚本将用户请求的本地日期和时间以及用户所在时区与 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 runat="server">
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" runat="server">
<title>Parsing a Date and Time Value</title>
</head>
<body>
<form id="DateForm" runat="server">
<div>
<center>
<asp:Label ID="Label1" runat="server" Text="Enter a Date and Time:" Width="248px"></asp:Label>
<asp:TextBox ID="DateString" runat="server" Width="176px"></asp:TextBox><br />
</center>
<br />
<center>
<asp:Button ID="OKButton" runat="server" Text="Button"
OnClientClick="AddDateInformation()" onclick="OKButton_Click" />
<asp:HiddenField ID="DateInfo" Value="" runat="server" />
</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 runat="server">
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" runat="server">
<title>Parsing a Date and Time Value</title>
</head>
<body>
<form id="DateForm" runat="server">
<div>
<center>
<asp:Label ID="Label1" runat="server" Text="Enter a Date and Time:" Width="248px"></asp:Label>
<asp:TextBox ID="DateString" runat="server" Width="176px"></asp:TextBox><br />
</center>
<br />
<center>
<asp:Button ID="OKButton" runat="server" Text="Button"
OnClientClick="AddDateInformation()" onclick="OKButton_Click" />
<asp:HiddenField ID="DateInfo" Value="" runat="server" />
</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。与此相比,Parse 方法可能会不断引发异常,在 Web 应用程序中,处理这些异常将是一种非常占用资源的做法。
编译代码
若要编译该代码,请创建一个没有代码隐藏文件的 ASP.NET 网页。然后将该示例复制到该网页中,以替换所有的现有代码。该 ASP.NET 网页应包含下列控件:
名为 DateString 的 TextBox 控件。
名为 DateInfo 的 HiddenField 控件。
安全性
若要防止用户将脚本注入 HTML 流中,切勿在服务器响应中直接回送用户输入。而是应改用 HttpServerUtility.HtmlEncode 方法回送用户输入。