Практическое руководство. Отображение сведений о локализованной дате и времени для веб-пользователей

Поскольку веб-страница может отображаться в любой точке мира, то при взаимодействии с пользователем операции, которые производят разбор и форматирование значений даты и времени, не должны базироваться на формате по умолчанию (которым чаще всего является формат локального веб-сервера). Вместо этого, веб-формы, которые обрабатывают строки даты и времени, введенные пользователем, должны производить разбор строк с использованием выбранных пользователем языка и региональных параметров. Аналогично данные о дате и времени следует отображать пользователю в формате, который соответствует его пользовательскому языку и региональным параметрам. В этом разделе показано, как это сделать.

Для разбора строк даты и времени, введенных пользователем

  1. Определите, заполняется ли массив строк, возвращаемый свойством HttpRequest.UserLanguages. Если это не так, то перейдите к шагу 6.

  2. Если массив строк, возвращаемый свойством UserLanguages заполнен, то извлеките его первый элемент. Первый элемент указывает на язык и регион по умолчанию или выбранный пользователем.

  3. Создайте объект CultureInfo, представляющий выбранные пользователем язык и региональные параметры, путем вызова конструктора CultureInfo.CultureInfo(String, Boolean).

  4. Вызовите метод TryParse, либо метод Parse типа DateTime или DateTimeOffset, чтобы выполнить преобразование. Используйте перегрузку метода TryParse или Parse с параметром provider и передайте ему один из следующих объектов:

  5. Если выполнить преобразование не удалось, повторите шаги со второго по четвертый для каждого оставшегося элемента в массиве строк, возвращаемого свойством UserLanguages.

  6. Если преобразование по-прежнему не удается или если массив строк, возвращаемый свойством UserLanguages, пуст, то произведите разбор строки с использованием инвариантных региональных параметров, которые возвращаются свойством CultureInfo.InvariantCulture.

Для разбора локальной даты и времени запроса пользователя

  1. Добавьте элемент управления HiddenField в веб-форму.

  2. Создайте функцию JavaScript, которая будет обрабатывать событие onClick от кнопки Submit, записывая текущую дату и время, а также смещение местного часового пояса от универсального синхронизированного времени (UTC) в свойство Value. Используйте разделители (такие как точка с запятой) для разделения двух компонентов строки.

  3. Используйте событие PreRender веб-формы для вставки этой функции в выходной поток HTML, передав текст скрипта методу ClientScriptManager.RegisterClientScriptBlock(Type, String, String, Boolean).

  4. Соедините обработчик событий с событием onClick кнопки Submit, предоставив имя функции JavaScript атрибуту OnClientClick кнопки Submit.

  5. Создайте обработчик для события Click кнопки Submit.

  6. В обработчике событий определите, заполняется ли массив строк, возвращаемый свойством HttpRequest.UserLanguages. Если это не так, то перейдите к шагу 14.

  7. Если массив строк, возвращаемый свойством UserLanguages заполнен, то извлеките его первый элемент. Первый элемент указывает на язык и регион по умолчанию или выбранный пользователем.

  8. Создайте объект CultureInfo, представляющий выбранные пользователем язык и региональные параметры, путем вызова конструктора CultureInfo.CultureInfo(String, Boolean).

  9. Передайте строку, присвоенную свойству Value, методу Split для сохранения строкового представления локальной даты и времени пользователя и строкового представления смещения местного часового пояса пользователя в отдельных элементах массива.

  10. Вызовите либо метод DateTime.Parse, либо метод DateTime.TryParse(String, IFormatProvider, DateTimeStyles, DateTime) для преобразования даты и времени запроса пользователя в значение DateTime. Используйте перегруженный метод с параметром provider и передайте ему одно из нижеследующего:

  11. Если операция анализа на шаге 10 завершается сбоем, перейдите к шагу 13. В противном случае вызовите метод UInt32.Parse(String) для преобразования строкового представления смещения часового пояса пользователя в целое число.

  12. Создайте экземпляр DateTimeOffset, представляющий локальное время пользователя, вызвав конструктор DateTimeOffset.DateTimeOffset(DateTime, TimeSpan).

  13. Если преобразование на шаге 10 не удалось, то повторите шаги с 7 по 12 для каждого оставшегося элемента в массиве строк, возвращаемого свойством UserLanguages.

  14. Если преобразование по-прежнему не удается или если массив строк, возвращаемый свойством UserLanguages, пуст, то произведите разбор строки с использованием инвариантных региональных параметров, которые возвращаются свойством CultureInfo.InvariantCulture. Затем повторите шаги с 7 по 12.

В результате появится объект DateTimeOffset, представляющий локальное время пользователя веб-страницы. Затем можно определить эквивалент UTC, вызвав метод ToUniversalTime. Можно также определить эквивалентную дату и время на веб-сервере, вызвав метод TimeZoneInfo.ConvertTime(DateTimeOffset, TimeZoneInfo) и передав ему значение TimeZoneInfo.Local как часовой пояс, к которому требуется преобразовать время.

Пример

Следующий пример содержит исходный код HTML и код для веб-формы ASP.NET, который запрашивает у пользователя ввод значения даты и времени. Клиентский скрипт записывает сведения о локальной дате и времени запроса пользователя, а также смещение часового пояса пользователя от времени 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 заполняется из имен языка и региональных параметров, содержащихся в заголовках Accept-Language, включенных в HTTP-запрос. Однако не все обозреватели включают заголовки Accept-Language в свои запросы, кроме того, пользователи могут полностью запретить заголовки. В связи с этим при разборе данных, введенных пользователем, возникает необходимость иметь резервный набор региональных параметров. Обычно резервный язык и региональные параметры являются инвариантными региональными параметрами, возвращаемые CultureInfo.InvariantCulture. Пользователи также могут ввести в Internet Explorer название языка и региональных параметров, которое они вводят в текстовое поле, а это создает вероятность того, что такое название может оказаться недопустимым. Всвязи с этим возникает необходимость обработки исключений при создании объекта CultureInfo.

При извлечении из HTTP-запроса, посланного Internet Explorer, массив HttpRequest.UserLanguages заполняется в порядке, определенном в пользовательских настройках. Первый элемент массива содержит название основного национального языка/региона пользователя. Если массив содержит другие элементы, то Internet Explorer произвольно назначит им спецификатор качества, который отделяется от имени региональных параметров точкой с запятой. Например, запись региональных параметров fr-FR может принять форму fr-FR;q=0.7.

В примере вызывается конструктор CultureInfo с параметром useUserOverride с настройкой falseдля создания нового объекта CultureInfo. В этом случае, если региональные параметры по умолчанию используются на сервере, то новый объект CultureInfo, созданный конструктором класса, содержит все региональные параметры по умолчанию и не зависит от настроек, заданных при помощи серверного приложения Язык и региональные параметры. Значения всех скорректированных настроек на сервере вряд ли появятся на компьютере пользователя или отразятся в данных, введенных пользователем.

Поскольку в этом примере выполняется разбор двух строковых представлений даты и времени (одно, введенное пользователем, другое, сохраненное в скрытом поле), то здесь определяются возможные объекты CultureInfo, которые могут понадобиться заранее. Создается массив объектов CultureInfo, который содержит на один элемент больше, чем число элементов, возвращенных свойством HttpRequest.UserLanguages. Затем создается объект CultureInfo для каждой строки языка/региона, а также создается объект CultureInfo, представляющий CultureInfo.InvariantCulture.

Код может вызывать либо метод Parse, либо метод TryParse для преобразования пользовательского строкового представления даты и времени в значение DateTime. Для отдельной операции разбора может потребоваться повторный вызов метода синтаксического анализа. В результате метод TryParse оказывается эффективней, поскольку он возвращает false при ошибке в выполнении операции синтаксического анализа. Напротив, обработка повторяющихся исключений, которые могут посылаться методом Parse, может быть слишком дорогостоящей задачей для веб-приложений.

Компиляция кода

Для компиляции кода создайте веб-страницу ASP.NET без программной части (code-behind). Затем скопируйте пример в веб-страницу таким образом, чтобы он заменил весь существующий код. Веб-страница ASP.NET должна содержать следующие элементы управления:

  • Элемент управления Label, который не указан в коде. Присвойте его свойству Text значение "Введите число:".

  • Элемент управления TextBox с именем DateString.

  • Элемент управления Button с именем OKButton. Присвойте его свойству Text значение "OK".

  • Элемент управления HiddenField с именем DateInfo.

Безопасность

Чтобы предотвратить добавление скрипта в поток HTML пользователем, вводимые данные никогда не должны непосредственно передаваться обратно в ответе сервера. Вместо этого они должны быть зашифрованы с помощью метода HttpServerUtility.HtmlEncode.

См. также

Основные понятия

Выполнение операций форматирования

Строки стандартных форматов даты и времени

Строки настраиваемых форматов даты и времени

Разбор строк даты и времени