Cómo: Mostrar información localizada de fecha y hora a usuarios web

Actualización: noviembre 2007

Dado que una página web puede mostrarse en cualquier parte del mundo, las operaciones de análisis y formato que se realizan sobre los valores de fecha y hora no deben basarse en un formato predeterminado (que suele ser el formato de la referencia cultural local del servidor web) al interactuar con el usuario. En lugar de ello, los formularios web que controlan cadenas de fecha y hora escritas por el usuario deben analizar estas cadenas usando la referencia cultural preferida del usuario. De igual forma, los datos de fecha y hora deben mostrarse al usuario en un formato que se ajuste a su referencia cultural. En este tema se explica cómo hacerlo.

Para analizar cadenas de fecha y hora escritas por el usuario

  1. Determine si la matriz de cadenas devuelta por la propiedad HttpRequest.UserLanguages contiene elementos. Si no es así, continúe en el paso 6.

  2. Si la matriz de cadenas devuelta por la propiedad UserLanguages contiene elementos, recupere el primer elemento. El primer elemento indica el idioma y la región predeterminados o preferidos del usuario.

  3. Cree una instancia de un objeto CultureInfo que represente la referencia cultural preferida del usuario; para ello, llame al constructor CultureInfo.CultureInfo(String, Boolean).

  4. Llame al método TryParse o Parse del tipo DateTime o DateTimeOffset para intentar realizar la conversión. Utilice una sobrecarga del método TryParse o Parse con un parámetro provider y pásele cualquiera de los objetos siguientes:

  5. Si la conversión no se realiza correctamente, repita los pasos del 2 al 4 en cada elemento restante de la matriz de cadenas devuelta por la propiedad UserLanguages.

  6. Si la conversión sigue produciendo errores o si la matriz de cadenas devuelta por la propiedad UserLanguages está vacía, analice la cadena usando la referencia cultural de todos los idiomas devuelta por la propiedad CultureInfo.InvariantCulture.

Para analizar la fecha y hora locales de la solicitud del usuario

  1. Agregue un control HiddenField a un formulario web.

  2. Cree una función JavaScript que controle el evento onClick de un botón Submit; para ello, especifique la fecha y hora actuales y el desfase de la zona horaria local con respecto a la hora universal coordinada (hora UTC) en la propiedad Value. Use un delimitador (por ejemplo, un punto y coma) para separar los dos componentes de la cadena.

  3. Use el evento PreRender del formulario web para insertar la función en la secuencia de salida HTML; para ello, pase el texto del script al método ClientScriptManager.RegisterClientScriptBlock(Type, String, String, Boolean).

  4. Asocie el controlador de eventos con el evento onClick del botón Submit; para ello, especifique el nombre de la función JavaScript en el atributo OnClientClick del botón Submit.

  5. Cree un controlador para el evento Click del botón Submit.

  6. En el controlador de eventos, determine si la matriz de cadenas devuelta por la propiedad HttpRequest.UserLanguages contiene elementos. Si no es así, continúe en el paso 14.

  7. Si la matriz de cadenas devuelta por la propiedad UserLanguages contiene elementos, recupere el primer elemento. El primer elemento indica el idioma y la región predeterminados o preferidos del usuario.

  8. Cree una instancia de un objeto CultureInfo que represente la referencia cultural preferida del usuario; para ello, llame al constructor CultureInfo.CultureInfo(String, Boolean).

  9. Pase la cadena asignada a la propiedad Value al método Split para almacenar la representación de cadena de la fecha y hora locales del usuario y la representación de cadena del desfase de la zona horaria local del usuario en diferentes elementos de la matriz.

  10. Llame al método DateTime.Parse o DateTime.TryParse(String, IFormatProvider, DateTimeStyles, DateTime%) para convertir la fecha y hora de la solicitud del usuario en un valor DateTime. Utilice una sobrecarga del método con un parámetro provider y pásele cualquiera de los objetos siguientes:

  11. Si la operación de análisis del paso 10 no se realiza correctamente, vaya al paso 13. De lo contrario, llame al método UInt32.Parse(String) para convertir la representación de cadena del desfase de la zona horaria del usuario en un número entero.

  12. Cree una instancia de DateTimeOffset que represente la hora local del usuario llamando al constructor DateTimeOffset.DateTimeOffset(DateTime, TimeSpan).

  13. Si la conversión del paso 10 no se realiza correctamente, repita los pasos del 7 al 12 en cada elemento restante de la matriz de cadenas devuelta por la propiedad UserLanguages.

  14. Si la conversión sigue produciendo errores o si la matriz de cadenas devuelta por la propiedad UserLanguages está vacía, analice la cadena usando la referencia cultural de todos los idiomas devuelta por la propiedad CultureInfo.InvariantCulture. A continuación, repita los pasos del 7 al 12.

El resultado es un objeto DateTimeOffset que representa la hora local del usuario de su página web. Puede determinar a continuación la hora UTC equivalente llamando al método ToUniversalTime. También puede determinar la fecha y hora equivalentes del servidor web llamando al método TimeZoneInfo.ConvertTime(DateTimeOffset, TimeZoneInfo) y pasando un valor TimeZoneInfo.Local como zona horaria a la que debe convertirse la hora.

Ejemplo

El ejemplo siguiente incluye código fuente HTML y el código de un formulario web de ASP.NET que solicita al usuario que escriba un valor de fecha y hora. También hay un script de cliente que escribe información sobre la fecha y hora local de la solicitud del usuario y el desfase de la zona horaria del usuario con respecto a la hora UTC en un campo oculto. Esta información se analiza a continuación en el servidor, que devuelve una página web en la que se muestran los datos especificados por el usuario. También muestra la fecha y hora de la solicitud del usuario utilizando la hora local del usuario, la hora del servidor y la hora 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>

El script de cliente llama al método toLocaleString de JavaScript. Esto genera una cadena que se ajusta a las convenciones de formato de la configuración regional del usuario, por lo que es más probable que se analice correctamente en el servidor.

La propiedad HttpRequest.UserLanguages se rellena a partir de los nombres de la referencia cultural incluidos en los encabezados Accept-Language de una solicitud HTTP. Sin embargo, no todos los exploradores incluyen encabezados Accept-Language en sus solicitudes y, además, los usuarios pueden suprimir los encabezados por completo. Por ello, es importante tener una referencia cultural de reserva al analizar los datos proporcionados por el usuario. Normalmente, la referencia cultural de reserva es la referencia cultural de todos los idiomas devuelta por la propiedad CultureInfo.InvariantCulture. Los usuarios también pueden proporcionar nombres de referencia cultural a Internet Explorer escribiéndolos en un cuadro de texto, lo que introduce la posibilidad de que los nombres de referencia cultural no sean válidos. Esto hace que sea importante utilizar el control de excepciones a la hora de crear instancias de un objeto CultureInfo.

La matriz HttpRequest.UserLanguages, cuando se recupera a partir de una solicitud HTTP enviada por Internet Explorer, se rellena en el orden de preferencia del usuario. El primer elemento de la matriz contiene el nombre de la referencia cultural o región principal del usuario. Si la matriz contiene otros elementos, Internet Explorer les asigna un especificador de calidad de forma arbitraria, separado del nombre de la referencia cultural mediante un punto y coma. Por ejemplo, una entrada para la referencia cultural fr-FR podría presentar la forma fr-FR;q=0.7.

En el ejemplo, se llama al constructor CultureInfo con el parámetro useUserOverride establecido en false para crear un nuevo objeto CultureInfo. De este modo, tiene la seguridad de que si el nombre de la referencia cultural es el nombre de la referencia cultural predeterminada del servidor, el nuevo objeto CultureInfo creado por el constructor de clase contiene la configuración predeterminada de una referencia cultural y no refleja las configuraciones invalidadas con la aplicación Configuración regional y de idioma del servidor. Es poco probable que los valores de una configuración invalidada en el servidor permanezcan en el sistema del usuario o se reflejen en los datos proporcionados por el usuario.

Dado que en este ejemplo se analizan dos representaciones de cadena de una fecha y hora (un valor proporcionado por el usuario y otro valor almacenado en el campo oculto), se definen los objetos CultureInfo que a priori podrían ser necesarios. En el ejemplo se crea una matriz de objetos CultureInfo con un elemento más que el número de elementos devuelto por la propiedad HttpRequest.UserLanguages. A continuación, se crean instancias de un objeto CultureInfo para cada cadena de idioma o región y también se crean instancias de un objeto CultureInfo que representa CultureInfo.InvariantCulture.

Su código puede llamar al método Parse o TryParse para convertir la representación de cadena del usuario de una fecha y hora en un valor DateTime. Es posible que sea necesario llamar varias veces a un método de análisis en una única operación de análisis. Como consecuencia, el método TryParse resulta más conveniente porque devuelve false si se producen errores en una operación de análisis. Por el contrario, mantener un control sobre las excepciones repetidas que puede iniciar el método Parse puede ser una tarea excesivamente costosa cuando se trata de una aplicación web.

Compilar el código

Para compilar el código, cree una página web de ASP.NET sin código subyacente. A continuación, copie el ejemplo en la página web de modo que reemplace todo el código existente. La página web de ASP.NET debe contener los controles siguientes:

  • Un control Label, al que no se hace referencia en el código. Establezca la propiedad Text en "Escriba un número:".

  • Un control TextBox denominado DateString.

  • Un control Button denominado OKButton. Establezca la propiedad Text en "Aceptar".

  • Un control HiddenField denominado DateInfo.

Seguridad

Para impedir que un usuario inserte el script en la secuencia HTML, los datos proporcionados por el usuario nunca deben devolverse directamente en la respuesta del servidor. En lugar de ello, deben codificarse con el método HttpServerUtility.HtmlEncode.

Vea también

Conceptos

Temas "Cómo..." sobre formatos

Cadenas de formato de fecha y hora

Analizar cadenas de fecha y hora