方法 : ローカライズされた日付/時刻情報を Web ユーザーに表示する
更新 : 2007 年 11 月
Web ページは世界中どこでも表示可能なため、日付/時刻値を解析して書式設定する操作では、ユーザーと対話する際に既定の書式 (ほとんどの場合、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 プロパティによって返されるインバリアント カルチャを使って文字列を解析します。
ユーザーが要求したローカル日付/時刻を解析するには
HiddenField コントロールを Web フォームに追加します。
現在の日付/時刻、および世界協定時刻 (UTC) からのローカル タイム ゾーンのオフセットを Value プロパティに書き込むことによって、Submit ボタンの onClick イベントを処理する JavaScript 関数を作成します。文字列の 2 つの構成要素を分けるために、区切り記号 (たとえばセミコロン) を使用します。
スクリプトのテキストを ClientScriptManager.RegisterClientScriptBlock(Type, String, String, Boolean) メソッドに渡すことにより、Web フォームの PreRender イベントを使用して HTML 出力ストリームの中に関数を挿入します。
JavaScript 関数の名前を Submit ボタンの OnClientClick 属性に渡すことにより、イベント ハンドラを Submit ボタンの onClick イベントに接続します。
Submit ボタンの Click イベントのハンドラを作成します。
イベント ハンドラ内で、HttpRequest.UserLanguages プロパティによって返される文字列配列にデータが入っているかどうかを判別します。入っていない場合は、手順 14. に進みます。
UserLanguages プロパティによって返された文字列配列にデータが含まれる場合は、その最初の要素を取得します。最初の要素は、ユーザーの既定の言語と地域、または優先される言語と地域を表します。
CultureInfo.CultureInfo(String, Boolean) コンストラクタを呼び出すことにより、ユーザーが選択したカルチャを表す CultureInfo オブジェクトをインスタンス化します。
ユーザーのローカル日付/時刻の文字列表現、およびユーザーのローカル タイム ゾーン オフセットの文字列表現を別々の配列要素に保存するために、Value プロパティに割り当てられた文字列を Split メソッドに渡します。
ユーザー側の日付/時刻を DateTime 値に変換するために、DateTime.Parse メソッドまたは DateTime.TryParse(String, IFormatProvider, DateTimeStyles, 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. を繰り返します。
結果として、Web ページのユーザー側のローカル時間を表す DateTimeOffset オブジェクトが生成されます。その後、ToUniversalTime メソッドを呼び出すことで、対応する UTC を判別できます。また、TimeZoneInfo.ConvertTime(DateTimeOffset, TimeZoneInfo) メソッドを呼び出し、時間の変換先のタイム ゾーンとして TimeZoneInfo.Local の値を渡すことによって、Web サーバー側の対応する日付/時刻を判別することもできます。
使用例
次の例は、日付/時刻値の入力をユーザーに求める ASP.NET Web フォームの HTML ソースとコードの両方を含んでいます。また、クライアント側スクリプトは、ユーザー側のローカル日付/時刻に関する情報、およびユーザーのタイム ゾーンと UTC とのオフセットに関する情報を隠しフィールドに書き込みます。その後、この情報はサーバーによって解析され、ユーザー入力を表示する Web ページが返されます。さらに、ユーザーのローカル時間、サーバーの時間、および 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 という形式になります。
この例では、useUserOverride パラメータを false に設定して CultureInfo コンストラクタを呼び出し、新しい CultureInfo オブジェクトを作成します。これによって、カルチャ名がサーバー上の既定のカルチャ名である場合、クラス コンストラクタによって作成される新しい CultureInfo オブジェクトにはカルチャの既定の設定が含まれ、サーバーの [地域と言語のオプション] アプリケーションを使ってオーバーライドされた設定は反映されません。通常、サーバーでオーバーライドされた設定値はユーザーのシステムに存在せず、ユーザー入力に反映されることもありません。
この例では 1 つの日付/時刻の 2 つの文字列表現 (1 つはユーザー入力、もう 1 つは隠しフィールドに格納されたもの) を解析するため、必要になる可能性のある CultureInfo オブジェクトが事前に定義されます。HttpRequest.UserLanguages プロパティによって返される要素数より 1 つ多い CultureInfo オブジェクトから成る配列が作成されます。その後、それぞれの言語/地域文字列ごとに 1 つの CultureInfo オブジェクトがインスタンス化され、CultureInfo.InvariantCulture を表す CultureInfo オブジェクトもインスタンス化されます。
ユーザーの日付/時刻の文字列表現を DateTime 値に変換するために、コードの中で Parse メソッドまたは TryParse メソッドのどちらでも呼び出すことができます。1 つの解析操作で解析メソッドを繰り返し呼び出す必要が生じる場合があります。このため、解析操作が失敗した場合に false を返す TryParse メソッドを使用する方が適切です。これに対して、Parse メソッドによって繰り返しスローされる例外を Web アプリケーションで処理する場合、コストが非常に大きくなる可能性があります。
コードのコンパイル方法
コードをコンパイルするには、分離コードのない ASP.NET Web ページを作成します。その後、この例を Web ページ内にコピーして、既存のすべてのコードを置き換えます。ASP.NET Web ページには、次のコントロールを含める必要があります。
コード内で参照されない Label コントロール。その Text プロパティを "Enter a Number:" に設定します。
DateString という名前の TextBox コントロール。
DateInfo という名前の HiddenField コントロール。
セキュリティ
ユーザーが HTML ストリーム内にスクリプトを挿入するのを防ぐために、サーバー応答の中でユーザー入力を直接エコー バックしないでください。そうする代わりに、HttpServerUtility.HtmlEncode メソッドを使ってこれをエンコードする必要があります。