Compartir a través de


Codificación de procedimientos recomendados con DateTime en .NET Framework

 

Dan Rogers
Microsoft Corporation

Febrero de 2004

Se aplica a
   Microsoft® .NET Framework
   Servicios web de Microsoft® ASP.NET
   serialización XML

Resumen: La escritura de programas que almacenan, realizan cálculos y serializan valores de hora mediante el tipo DateTime de Microsoft .NET Framework requiere un conocimiento de los diferentes problemas asociados con las representaciones de tiempo disponibles en Windows y .NET. Este artículo se centra en los escenarios clave de pruebas y desarrollo que implican tiempo y define las recomendaciones de procedimientos recomendados para escribir programas que usan el tipo DateTime en Microsoft . Ensamblados y aplicaciones basados en NET. (18 páginas impresas)

Contenido

Información previa
   ¿Qué es dateTime, de todos modos?
   Reglas
Estrategias de almacenamiento
   Procedimiento recomendado n.º 1
   Procedimiento recomendado n.º 2
Realización de cálculos
   No vuelvas a engañarte
   Procedimiento recomendado n.º 3
   Ordenar métodos DateTime
El caso especial de XML
   Procedimiento recomendado n.º 4
   Procedimiento recomendado n.º 5
La clase Coders Quandary
   Procedimiento recomendado n.º 6
Trabajar con el horario de verano
   Procedimiento recomendado n.º 7
Formato y análisis de valores de User-Ready
   Consideración futura
Problemas con el método DateTime.Now()
   Procedimiento recomendado n.º 8
Un par de extras poco conocidos
Conclusión

Información previa

Muchos programadores encuentran asignaciones que requieren que almacenen y procesen con precisión los datos que contienen información de fecha y hora. A primera vista, el tipo de datos DateTime de Common Language Runtime (CLR) parece ser perfecto para estas tareas. No es raro, sin embargo, para los programadores, pero es más probable que los evaluadores encuentren casos en los que un programa simplemente pierde el seguimiento de los valores de tiempo correctos. Este artículo se centra en los problemas asociados a la lógica que implica DateTime y, al hacerlo, descubre los procedimientos recomendados para escribir y probar programas que capturan, almacenan, recuperan y transmiten información de DateTime.

¿Qué es dateTime, de todos modos?

Al examinar la documentación de la biblioteca de clases de NET Framework, vemos que "El tipo de valor System.DateTime de CLR representa fechas y horas que van desde las 12:00:00 medianoche, 1 de enero de 0001 AD a 11:59:59 p. m., 31 de diciembre de 99999 AD". Al leer más, aprendemos, sin duda, que un valor DateTime representa un instante en un momento dado y que una práctica común es registrar valores de un momento dado en la hora universal coordinada (UCT), más comúnmente conocidos como Hora media de Greenwich (GMT).

A primera vista, un programador detecta que un tipo DateTime es bastante bueno al almacenar valores de hora que probablemente se encuentren en problemas de programación actuales, como en aplicaciones empresariales. Con esta confianza, muchos programadores no especificados comienzan a codificar, seguro de que pueden aprender tanto como necesiten sobre el tiempo a medida que avanzan. Este enfoque de "aprendizaje por uso" puede provocar algunos problemas, por lo que vamos a empezar a identificarlos. Van desde los problemas de la documentación hasta los comportamientos que deben tenerse en cuenta en los diseños de programa.

La documentación de la versión 1.0 y 1.1 de System.DateTime hace que algunas generalizaciones puedan producir el programador no especificado fuera de la pista. Por ejemplo, la documentación sigue afirmando que los métodos y propiedades que se encuentran en la clase DateTime siempre usan la suposición de que el valor representa la zona horaria local del equipo local al realizar cálculos o comparaciones. Esta generalización resulta ser falsa porque hay ciertos tipos de cálculos de fecha y hora que asumen GMT, y otros que asumen una vista de zona horaria local. Estas áreas se señalan más adelante en este artículo.

Por lo tanto, vamos a empezar explorando el tipo DateTime mediante la esquematización de una serie de reglas y procedimientos recomendados que pueden ayudarle a que el código funcione correctamente la primera vez.

Reglas

  1. Los cálculos y comparaciones de instancias de DateTime solo son significativos cuando las instancias que se comparan o usan son representaciones de puntos en el tiempo desde la misma perspectiva de zona horaria.
  2. Un desarrollador es responsable de realizar un seguimiento de la información de zona horaria asociada a un valor DateTime a través de algún mecanismo externo. Normalmente, esto se logra definiendo otro campo o variable que se usa para registrar información de zona horaria al almacenar un tipo de valor DateTime. Este enfoque (almacenar el sentido de zona horaria junto con el valor DateTime) es el más preciso y permite a los distintos desarrolladores en distintos puntos del ciclo de vida de un programa tener siempre una comprensión clara del significado de un valor DateTime. Otro enfoque común es convertirla en una "regla" en el diseño que todos los valores de tiempo se almacenan en un contexto de zona horaria específico. Este enfoque no requiere almacenamiento adicional para guardar la vista de un usuario del contexto de zona horaria, pero presenta el riesgo de que un valor de hora se malinterprete o se almacene incorrectamente en el camino por parte de un desarrollador que no conoce la regla.
  3. Es posible que la realización de cálculos de fecha y hora en los valores que representan la hora local del equipo no siempre produzca el resultado correcto. Al realizar cálculos en valores de hora en contextos de zona horaria que practiquen el horario de verano, debe convertir valores en representaciones de hora universales antes de realizar cálculos aritméticos de fecha. Para obtener una lista específica de las operaciones y los contextos de zona horaria adecuados, consulte la tabla de la sección Sorting out DateTime Methods (Ordenar métodos DateTime).
  4. Un cálculo en una instancia de un valor DateTime no modifica el valor de la instancia, por lo que una llamada a MyDateTime.ToLocalTime() no modifica el valor de la instancia de DateTime. Los métodos asociados a las clases Date (en Visual Basic®) y DateTime (en .NET CLR) devuelven nuevas instancias que representan el resultado de un cálculo o una operación.
  5. Al usar .NET Framework versión 1.0 y 1.1, NO envíe un valor DateTime que represente la hora de UCT hasta System.XML. Serialización. Esto va para los valores Date, Time y DateTime. En el caso de los servicios web y otras formas de serialización a XML que implican System.DateTime, asegúrese siempre de que el valor del valor DateTime representa la hora local del equipo actual. El serializador descodificará correctamente un valor DateTime definido por el esquema XML codificado en GMT (valor de desplazamiento = 0), pero lo descodificará en el punto de vista de hora del equipo local.
  6. En general, si trabaja con un tiempo absoluto transcurrido, como medir un tiempo de espera, realizar comparaciones de diferentes valores DateTime, probar y usar un valor de hora universal si es posible para obtener la mejor precisión posible sin efectos de la zona horaria o el horario de verano que tienen un impacto.
  7. Cuando se trabaja con conceptos de alto nivel y orientados al usuario, como la programación, y se puede suponer de forma segura que cada día tiene 24 horas desde la perspectiva de un usuario, puede estar bien contrarrestar la regla #6 mediante la realización de aritmética, et cetera, en horas locales.

En este artículo, esta lista sencilla de reglas sirve como base para un conjunto de procedimientos recomendados para escribir y probar aplicaciones que procesan fechas.

Por ahora, varios de ustedes ya están viendo el código y diciendo: "Oh darn, no está haciendo lo que esperaba que hiciera", que es el propósito de este artículo. Para aquellos de nosotros que no han tenido una epifanía desde aquí, echemos un vistazo a los problemas asociados con el procesamiento de valores DateTime (de ahora en adelante, solo lo acortaré a "fechas") en . Aplicaciones basadas en NET.

Estrategias de almacenamiento

Según las reglas anteriores, los cálculos de los valores de fecha solo son significativos cuando comprende la información de zona horaria asociada al valor de fecha que está procesando. Esto significa que si va a almacenar el valor temporalmente en una variable miembro de clase o si elige guardar los valores que ha recopilado en una base de datos o archivo, usted como programador es responsable de aplicar una estrategia que permita que la información de zona horaria asociada se entienda más adelante.

Procedimiento recomendado n.º 1

Al codificar, almacene la información de zona horaria asociada a un tipo DateTime en una variable adjunct.

Una estrategia alternativa, pero menos confiable, es hacer una regla firme de que las fechas almacenadas siempre se convertirán en una zona horaria determinada, como GMT, antes del almacenamiento. Esto puede parecer razonable, y muchos equipos pueden hacer que funcione. Sin embargo, la falta de una señal excesiva que indica que una columna DateTime determinada de una tabla de una base de datos se encuentra en una zona horaria específica invariablemente conduce a errores en la interpretación en iteraciones posteriores de un proyecto.

Una estrategia común vista en una encuesta informal de diferentes . Las aplicaciones basadas en NET son el deseo de tener siempre fechas representadas en la hora universal (GMT). Digo "deseo" porque esto no siempre es práctico. Un caso en el punto surge al serializar una clase que tiene una variable miembro DateTime a través de un servicio web. El motivo es que un tipo de valor DateTime se asigna a un tipo XSD:DateTime (como cabría esperar) y el tipo XSD se adapta a los puntos de tiempo en cualquier zona horaria. Analizaremos el caso XML más adelante. Lo más interesante es que un buen porcentaje de estos proyectos no lograron realmente su objetivo y almacenaban la información de fecha en la zona horaria del servidor sin darse cuenta.

En estos casos, un hecho interesante es que los evaluadores no estaban viendo problemas de conversión de tiempo, por lo que nadie había observado que el código que se supone que debía convertir la información de fecha local a la hora de UCT estaba fallando. En estos casos específicos, los datos se serializaron posteriormente a través de XML y se convirtieron correctamente porque la información de fecha estaba en la hora local del equipo con la que empezar.

Echemos un vistazo a algún código que no funciona:

Dim d As DateTime
d = DateTime.Parse("Dec 03, 2003 12:00:00 PM") 'date assignment
d.ToUniversalTime()

El programa anterior toma el valor de la variable d y lo guarda en una base de datos, esperando que el valor almacenado represente una vista de tiempo de UCT. En este ejemplo se reconoce que el método Parse representa el resultado en tiempo local a menos que se use alguna referencia cultural no predeterminada como argumento opcional para la familia de métodos Parse.

El código mostrado anteriormente no puede convertir realmente el valor de la variable DateTime d a la hora universal en la tercera línea porque, como se ha escrito, el ejemplo infringe la regla #4 (los métodos de la clase DateTime no convierten el valor subyacente). Nota: Este código se vio en una aplicación real que se había probado.

¿Cómo pasó? Las aplicaciones implicadas pudieron comparar correctamente las fechas almacenadas porque, durante las pruebas, todos los datos provenían de máquinas establecidas en la misma zona horaria, por lo que se cumplió la regla 1 (todas las fechas que se comparan y calculan se localizan al mismo punto de vista de zona horaria). El error de este código es el tipo que es difícil de detectar: una instrucción que se ejecuta pero que no hace nada (sugerencia: la última instrucción del ejemplo es una operación sin operación como escrita).

Procedimiento recomendado n.º 2

Al realizar pruebas, compruebe que los valores almacenados representan el valor de un momento dado que tiene previsto en la zona horaria que pretende.

Corregir el ejemplo de código es fácil:

Dim d As DateTime
d = DateTime.Parse("Dec 03, 2003 12:00:00 PM").ToUniversalTime()

Dado que los métodos de cálculo asociados al tipo de valor DateTime nunca afectan al valor subyacente, sino que devuelven el resultado del cálculo, un programa debe recordar almacenar el valor convertido (si se desea, por supuesto). A continuación, examinaremos cómo incluso este cálculo aparentemente adecuado puede no lograr los resultados esperados en determinadas circunstancias que implican el horario de verano.

Realización de cálculos

De primera vista, las funciones de cálculo que vienen con la clase System.DateTime son realmente útiles. Se proporciona compatibilidad para agregar intervalos a valores de tiempo, realizar aritméticas en valores de tiempo e incluso convertir valores de hora de .NET al tipo de valor correspondiente adecuado para las llamadas API de Win32®, así como las llamadas de Automatización OLE. Un vistazo a los métodos de soporte técnico que rodean el tipo DateTime evoca un vistazo nostálgico a las diferentes formas en que MS-DOS® y Windows® han evolucionado para tratar con las marcas de tiempo y hora a lo largo de los años.

El hecho de que todos estos componentes siguen estando presentes en varias partes del sistema operativo está relacionado con los requisitos de compatibilidad con versiones anteriores que Mantiene Microsoft. Para un programador, esto significa que si va a mover datos que representan marcas de tiempo en archivos, directorios o realizar interoperabilidad COM/OLE que implique valores date y DateTime, tendrá que convertirse en experto en tratar con conversiones entre las distintas generaciones de tiempo que están presentes en Windows.

No te vuelvas a engañar

Supongamos que ha adoptado la estrategia "almacenamos todo en tiempo UCT", presumiblemente para evitar la sobrecarga de tener que almacenar un desplazamiento de zona horaria (y quizás una vista con los ojos del usuario de la zona horaria, como la hora estándar del Pacífico o PST). Hay varias ventajas para realizar cálculos mediante el tiempo de UCT. Jefe entre ellos es el hecho de que, cuando se representa en la hora universal, cada día tiene una longitud fija, y no hay desplazamientos de zona horaria con los que tratar.

Si te sorprendió leer que un día puede tener diferentes longitudes, ten en cuenta que en cualquier zona horaria que permita el horario de verano, en dos días del año (normalmente), los días tienen una longitud diferente. Por lo tanto, incluso si usa un valor de hora local, como la hora estándar del Pacífico (PST), si intenta agregar un intervalo de tiempo a un valor de instancia de DateTime específico, es posible que no obtenga el resultado que cree que debe si el intervalo que se va a agregar le lleva después del cambio a lo largo del tiempo en una fecha en la que el horario de verano se inicia o finaliza.

Veamos un ejemplo de código que no funciona en la zona horaria del Pacífico en el Estados Unidos:

Dim d As DateTime
d = DateTime.Parse("Oct 26, 2003 12:00:00 AM") 'date assignment
d = d.AddHours(3.0)
' - displays 10/26/2003 03:00:00 AM – an ERROR!
MsgBox(d.ToString)

El resultado que se muestra a partir de este cálculo puede parecer correcto a primera vista; sin embargo, el 26 de octubre de 2003, un minuto después de las 1:59 PST, el cambio del horario de verano surtió efecto. La respuesta correcta debe haber sido 10/26/2003, 02:00:00 AM, por lo que este cálculo basado en un valor de hora local no pudo producir el resultado correcto. Pero si miramos la Regla 3, parecemos tener una contradicción, pero no lo hacemos. Vamos a llamarlo un caso especial para usar los métodos Add/Subtract en zonas horarias que celebran el horario de verano.

Procedimiento recomendado n.º 3

Al codificar, tenga cuidado si necesita realizar cálculos de DateTime (agregar o restar) en valores que representan zonas horarias que practiquen el horario de verano. Se pueden producir errores de cálculo inesperados. En su lugar, convierta el valor de hora local en hora universal, realice el cálculo y vuelva a convertir para lograr la máxima precisión..

Corregir este código roto es sencillo:

Dim d As DateTime
d = DateTime.Parse("Oct 26, 2003 12:00:00 AM") 'date assignment
d = d.ToUniversalTime().AddHours(3.0).ToLocalTime()
' - displays 10/26/2003 02:00:00 AM – Correct!
MsgBox(d.ToString)
  

La manera más fácil de agregar intervalos de tiempo de forma confiable es convertir valores basados en tiempo local en tiempo universal, realizar los cálculos y, a continuación, volver a convertir los valores.

Ordenar métodos DateTime

En este artículo se describen los distintos métodos de clase System.DateTime. Algunos producen un resultado correcto cuando la instancia subyacente representa la hora local, algunas cuando representan la hora universal y otras todavía no requieren ninguna instancia subyacente en absoluto. Además, algunos son completamente independientes de la zona horaria (por ejemplo, AddYear, AddMonth). Para simplificar el conocimiento general de las suposiciones detrás de los métodos de soporte técnico de DateTime más comunes, se proporciona la tabla siguiente.

Para leer la tabla, considere el punto de vista inicial (entrada) y final (valor devuelto). En todos los casos, el método devuelve el estado final de llamar a un método. No se realiza ninguna conversión a la instancia subyacente de datos. También se proporcionan advertencias que describen excepciones o instrucciones útiles.

Nombre del método Punto de vista inicial Punto de vista final Advertencias
ToUniversalTime Hora local UTC No llame a en una instancia de DateTime que ya represente la hora universal
ToLocalTime UTC Hora local No llame a en una instancia de DateTime que ya represente la hora local.
ToFileTime Hora local   El método devuelve un INT64 que representa la hora del archivo Win32 (hora UCT)
FromFileTime   Hora local Método estático: no se requiere ninguna instancia. Toma un tiempo de INT64 UCT como entrada
ToFileTimeUtc

(solo V1.1)

UTC   El método devuelve un INT64 que representa una hora del archivo Win32 (hora UCT)
FromFileTimeUtc

(solo V1.1)

  UTC El método convierte la hora del archivo INT64 Win32 en una instancia de DateTime UCT.
Ahora   Hora local Método estático: no se requiere ninguna instancia. Devuelve un valor DateTime que representa la hora actual en hora del equipo local.
UtcNow   UTC Método estático: no se requiere ninguna instancia
IsLeapYear Hora local   Devuelve un valor booleano que indica true si la parte del año de la instancia de hora local es un año bisiesto.
Hoy   Hora local Método estático: no se requiere ninguna instancia. Devuelve un valor DateTime establecido en Medianoche del día actual en la hora del equipo local.

El caso especial de XML

Varias personas a las que he hablado recientemente tenían el objetivo de diseño de serializar valores de hora a través de servicios web de modo que el XML que representa la fecha y hora se formatearía en GMT (por ejemplo, con un desplazamiento cero). Aunque he oído varias razones que van desde el deseo de simplemente analizar el campo como una cadena de texto para que se muestre en un cliente a querer conservar las suposiciones "almacenadas en UCT" que existen en el servidor a los autores de llamadas de los servicios web, no he sido convencido de que alguna vez hay una buena razón para controlar el formato de serialización en la conexión a este grado. ¿Por qué? Simplemente porque la codificación XML de un tipo DateTime es perfectamente adecuada para representar un instante en el tiempo, y el serializador XML integrado en .NET Framework realiza un buen trabajo para administrar los problemas de serialización y deserialización asociados con los valores de hora.

Además, resulta que forzar el System.XML. El serializador de serialización para codificar un valor de fecha en GMT en la conexión no es posible en .NET, al menos hoy no. Como programador, diseñador o administrador de proyectos, el trabajo se convierte en asegurarse de que los datos que se pasan en la aplicación se realizan con precisión con un mínimo de costo.

Varios de los grupos con los que hablé en la investigación que entró en este documento habían adoptado la estrategia de definir clases especiales y escribir sus propios serializadores XML para que tengan control total sobre lo que los valores DateTime de la conexión parecían en su XML. Si bien admiro la mierda que los desarrolladores tienen al dar el salto a esta empresa valiente, se asegura de que los matices de tratar con problemas de horario de verano y conversión de zona horaria por sí solo deben hacer que un buen administrador diga, "No hay forma", especialmente cuando los mecanismos proporcionados en .NET Framework realizan un trabajo perfectamente preciso de serializar los valores de tiempo ya.

Solo hay un truco que debe tener en cuenta y, como diseñador, debe comprenderlo y cumplir la regla (consulte Regla 5).

Código que no funciona:

Primero vamos a definir una clase XML simple con una variable miembro DateTime. Por integridad, esta clase es el equivalente simplificado del enfoque recomendado que se muestra más adelante en el artículo.

<XmlType(TypeName:="timeTestDef", _
    Namespace:= "http://tempuri.org/Timetester.xsd")>), _
    XmlRoot(), Serializable()> _
Public Class timeTestDef
    Private __timeVal As DateTime

    <XmlIgnore()> _
    Public timeValSpecified As Boolean

    <XmlElement(ElementName:="timeVal", IsNullable:=False, _
        Form:=XmlSchemaForm.Qualified, DataType:="dateTime", _
        Namespace:="http://tempuri.org/Timetester.xsd")> _
    Public Property timeVal() As DateTime
        Get
            timeVal = __timeVal
        End Get
        Set(ByVal Value As DateTime)
            __timeVal = Value
            timeValSpecified = True
        End Set
    End Property
End Class

Ahora, vamos a usar esta clase para escribir algún XML en un archivo.

' write out to the file
Dim t As Xml.XmlTextWriter
Dim ser As XmlSerializer
Dim tt As New timeTest ' a class that has a DateTime variable
' set the fields in your class
tt.timeVal = DateTime.Parse("12/12/2003 12:01:02 PM")
tt.timeVal = tt.TimeVal.ToUniversalTime()

' get a serializer for the root type, and serialize this UTC time
ser = New XmlSerializer(GetType(timeTest))
t = New Xml.XmlTextWriter("c:\timetest.xml", System.Text.Encoding.UTF8)
ser.Serialize(t, tt)
t.Close()
t = Nothing
tt = Nothing

Cuando se ejecuta este código, el XML que se serializa en el archivo de salida contiene una representación DateTime XML de la siguiente manera:

<timeVal>2003-12-12T20:01:02.0000000-08:00</timeVal> 

Este es un error: el valor codificado en el XML está desactivado en ocho horas. Puesto que esto sucede como el desplazamiento de zona horaria de mi máquina actual, deberíamos ser sospechosos. Mirando el propio XML, la fecha es correcta, y la fecha 20:01:02 corresponde a la hora del reloj en Londres para mi propia hora de mediodía, pero la parte de desplazamiento no es correcta para un reloj basado en Londres. Cuando el XML es similar a la hora de Londres, el desplazamiento también debe representar el punto de vista de Londres, que este código no logra.

El serializador XML siempre supone que los valores DateTime que se serializan representan la hora del equipo local, por lo que aplica el desplazamiento de zona horaria local de la máquina como parte de desplazamiento de la hora XML codificada. Cuando deserializamos esto en otra máquina, el desplazamiento original se resta del valor que se analiza y se agrega el desplazamiento de zona horaria de la máquina actual.

Cuando empezamos con una hora local, el resultado de la serialización (codificar en XML DateTime seguido de descodificación a la hora del equipo local) siempre es correcto, pero solo si el valor dateTime inicial que se serializa representa la hora local cuando comienza la serialización. En el caso de este ejemplo de código roto, ya habíamos ajustado el valor DateTime de la variable miembro timeVal a la hora de UCT, por lo que cuando serializamos y deserializamos, el resultado está desactivado por el número de horas iguales al desplazamiento de zona horaria de la máquina de origen. Esto es malo.

Procedimiento recomendado n.º 4

Al realizar pruebas, calcule el valor que espera ver en la cadena XML que se serializa mediante una vista de hora local de la máquina del momento en el que se está probando. Si el XML del flujo de serialización difiere, registre un error.

Corregir este código es sencillo. Comente la línea que llama a ToUniversalTime().

Procedimiento recomendado n.º 5

Al escribir código para serializar las clases que tienen variables miembro DateTime, los valores deben representar la hora local. Si no contienen la hora local, ajústelos antes de cualquier paso de serialización, incluidos el paso o la devolución de tipos que contienen valores DateTime en servicios web.

La clase Coders Quandary

Anteriormente hemos visto una clase bastante pocophisticada que expone una propiedad DateTime. En esa clase, simplemente serializamos lo que almacenamos en datetime, sin tener en cuenta si el valor representa un punto de vista de hora local o universal. Echemos un vistazo a un enfoque más sofisticado que ofrece a los programadores una opción excesiva en cuanto a las suposiciones de zona horaria que desean, mientras siempre serializan correctamente.

Al codificar una clase que tendrá una variable miembro de tipo DateTime, un programador tiene la opción de hacer pública la variable miembro o escribir la lógica de propiedad para encapsular la variable miembro con operaciones get/set . La elección de hacer que el tipo sea público tiene varias desventajas que, en el caso de los tipos DateTime, pueden tener consecuencias que no están bajo el control del desarrollador de clases.

Con lo que hemos aprendido hasta ahora, considere la posibilidad de proporcionar dos propiedades para cada tipo DateTime.

En el ejemplo siguiente se muestra el enfoque recomendado para administrar variables miembro DateTime:

<XmlType(TypeName:="timeTestDef", _
    Namespace:= "http://tempuri.org/Timetester.xsd")>), _
    XmlRoot(), Serializable(), _
    EditorBrowsable(EditorBrowsableState.Advanced)> _
Public Class timeTestDef
    Private __timeVal As DateTime

    <XmlIgnore()> _
    Public timeValSpecified As Boolean

    <XmlElement(ElementName:="timeVal", IsNullable:=False, _
        Form:=XmlSchemaForm.Qualified, DataType:="dateTime", _
        Namespace:="http://tempuri.org/Timetester.xsd")> _
    Public Property timeVal() As DateTime
        Get
            timeVal = __timeVal.ToLocalTime()
        End Get
        Set(ByVal Value As DateTime)
            __timeVal = Value.ToUniversalTime()
            timeValSpecified = True
        End Set
    End Property

    <XmlIgnore()> _
    Public Property timeValUTC() As DateTime
        Get
            timeValUTC = __timeVal
        End Get
        Set(ByVal Value As DateTime)
            __timeVal = Value
            timeValSpecified = True
        End Set
    End Property
End Class

Este ejemplo es el equivalente corregido al ejemplo de serialización de clase anterior. En ambos ejemplos de clase (este y el anterior), las clases son implementaciones que se describen con el esquema siguiente:

<?xml version="1.0" encoding="utf-8" ?> 
<xs:schema id="Timetester" 
     targetNamespace="http://tempuri.org/Timetester.xsd"
     elementFormDefault="qualified"
     xmlns="http://tempuri.org/Timetester.xsd"
     xmlns:mstns="http://tempuri.org/Timetester.xsd"
     xmlns:xs="http://www.w3.org/2001/XMLSchema">

     <xs:element name="timeTest" type="timeTestDef"/>
     <xs:complexType name="timeTestDef">
      <xs:sequence>
         <xs:element name="timeVal" type="xs:dateTime"/>
      </xs:sequence>
     </xs:complexType>
</xs:schema>

En este esquema, y en cualquier implementación de clase, definimos una variable miembro que representa un valor de hora opcional. En nuestro ejemplo recomendado, hemos proporcionado dos propiedades con captadores y establecedores, uno para la hora universal y otro para la hora local. Los atributos entre corchetes angulares que se ven en el código indican al serializador XML que use la versión de hora local para la serialización y, por lo general, hacer que la implementación de la clase resulte en la salida compatible con el esquema. Para que la clase se ocupe correctamente de la falta opcional de expresión cuando no se establece ningún valor en la instancia, la variable timeValSpecified y la lógica asociada en el establecedor de propiedades controla si el elemento XML se expresa en el momento de la serialización o no. Este comportamiento opcional aprovecha una característica del subsistema de serialización diseñado para admitir contenido XML opcional.

El uso de este enfoque para administrar valores DateTime en las clases de .NET proporciona lo mejor de ambos mundos: obtiene acceso de almacenamiento basado en la hora universal para que los cálculos sean precisos y obtenga la serialización adecuada de las vistas de hora local.

Procedimiento recomendado n.º 6

Al codificar, haga que las variables miembro DateTime sean privadas y proporcionen dos propiedades para manipular los miembros DateTime en una hora local o universal. Sesgar el almacenamiento en el miembro privado como tiempo de UCT mediante el control de la lógica en los captadores y establecedores. Agregue los atributos de serialización XML a la declaración de propiedad de hora local para asegurarse de que el valor de hora local es lo que se serializa (vea el ejemplo).

Advertencias a este enfoque

El enfoque recomendado para administrar datetime en hora universal dentro de las variables de miembro privado es sólido, como es la recomendación de proporcionar propiedades duales para permitir que los codificadores se ocupen de las versiones de tiempo con las que se sienten más cómodos. Un problema que un desarrollador usa este o cualquier otro enfoque que expone cualquier hora local a un programa sigue siendo el problema de 25 horas del día en torno al horario de verano. Esto seguirá siendo un problema para los programas que usan CLR versión 1.0 y 1.1, por lo que debe tener en cuenta si el programa entra en este caso especial (la hora agregada o faltante para el momento que se representa) y ajustar manualmente. Para aquellos que no pueden tolerar una ventana de problemas de una hora por año, la recomendación actual es almacenar las fechas como cadenas o algún otro enfoque autoadministrado. (Los enteros largos de Unix son una buena opción).

Para CLR versión 2.0 (disponible en la próxima versión de Visual Studio® code-named "Whidbey"), reconocimiento de si dateTime contiene una hora local o un valor de hora universal se agrega a .NET Framework. En ese momento, el patrón recomendado seguirá funcionando, pero para los programas que interactúan con variables miembro a través de las propiedades UTC, estos errores en el período de hora extra o falta se eliminarán. Por este motivo, el procedimiento recomendado para codificar mediante propiedades duales se recomienda en la actualidad, de modo que los programas se migren limpiamente a CLR versión 2.0.

Trabajar con el horario de verano

A medida que nos preparamos para cerrar y dejar el tema de procedimientos de codificación y pruebas para valores DateTime, sigue habiendo un caso especial que necesita comprender. Este caso implica las ambigüedades que rodean el horario de verano y el problema repetido de una hora al año. Este problema es principalmente uno que solo afecta a las aplicaciones que recopilan valores de tiempo de la entrada del usuario.

Para aquellos de ustedes en la mayoría del recuento de países, este caso es trivial porque en la mayoría de los países no se practica el horario de verano. Pero para aquellos de ustedes que se encuentran en la mayoría de los programas afectados (es decir, todos los que tienen aplicaciones que necesitan tratar con el tiempo que se pueden representar en lugares o que tienen origen en lugares en los que se practica el horario de verano), tiene que saber que este problema existe y tener en cuenta para ello.

En áreas del mundo que practican el horario de verano, hay una hora cada otoño y primavera donde el tiempo aparentemente va a haywire. En la noche en que la hora del reloj cambia de la hora estándar al horario de verano, el tiempo salta por delante de una hora. Esto ocurre en la primavera. En la caída del año, en una noche, el reloj de hora local salta una hora.

En estos días, puede encontrar condiciones en las que el día es de 23 o 25 horas de duración. Por lo tanto, si va a agregar o restar intervalos de tiempo de los valores de fecha y el intervalo cruza este punto extraño en el tiempo en el que cambian los relojes, el código debe realizar un ajuste manual.

Para la lógica que usa el método DateTime.Parse() para calcular un valor DateTime basado en la entrada del usuario de una fecha y hora específica, debe detectar que determinados valores no son válidos (en el día de 23 horas) y ciertos valores tienen dos significados porque una hora determinada se repite (en el día de 25 horas). Para ello, debe conocer las fechas implicadas y buscar estas horas. Puede ser útil analizar y volver a reproducir la información de fecha interpretada a medida que el usuario sale de los campos usados para escribir fechas. Como regla, evite que los usuarios especifiquen el horario de verano en su entrada.

Ya hemos tratado el procedimiento recomendado para los cálculos de intervalo de tiempo. Al convertir las vistas de hora local a la hora universal antes de realizar los cálculos, se pegan los problemas de precisión del tiempo. El caso más difícil de administrar es el caso de ambigüedad asociado a la entrada del usuario de análisis que se produce durante esta hora mágica en la primavera y otoño.

Actualmente no hay ninguna manera de analizar una cadena que represente la vista de tiempo de un usuario y que tenga asignado con precisión un valor de hora universal. La razón es que las personas que experimentan horario de verano no viven en lugares donde la zona horaria es la hora media de Greenwich. Por lo tanto, es totalmente posible que alguien que vive en la costa este de los tipos de Estados Unidos en un valor como "Oct 26, 2003 01:10:00 AM".

En esta mañana en particular, a las 2:00 a. m., el reloj local se restablece a las 1:00 a. m., creando un día de 25 horas. Dado que todos los valores de la hora del reloj entre las 1:00 a. m. y las 2:00 a. m. se producen dos veces en esa mañana en particular, al menos en la mayoría de los Estados Unidos y Canadá. El equipo realmente no tiene ninguna manera de saber qué 1:10 AM estaba pensado, el que se produce antes del conmutador, o el que se produce 10 minutos después del cambio de horario de verano.

Del mismo modo, los programas tienen que lidiar con el problema que ocurre en la primavera cuando, en una mañana determinada, no hay tiempo como las 2:10 a. m. La razón es que a las 2:00 de esa mañana en particular, la hora de los relojes locales cambia repentinamente a las 3:00 a. m. Toda la 2:00 horas nunca se produce en este día de 23 horas.

Los programas tienen que lidiar con estos casos, posiblemente llamando al usuario cuando detecte la ambigüedad. Si no recopila cadenas de fecha y hora de los usuarios y las analiza, es probable que no tenga estos problemas. Los programas que necesitan determinar si un horario determinado cae en horario de verano puede hacer uso de lo siguiente:

Timezone.CurrentTimeZone.IsDaylightSavingTime(DateTimeInstance)

o

DateTimeInstance.IsDaylightSavingTime

Procedimiento recomendado n.º 7

Al realizar pruebas, si los programas aceptan entradas de usuario que especifican valores de fecha y hora, asegúrese de probar la pérdida de datos en "spring-ahead", "reserva" de 23 y 25 días. Asegúrese también de probar las fechas recopiladas en una máquina en una zona horaria y almacenadas en una máquina en otra zona horaria.

Aplicar formato y analizar valores User-Ready

En el caso de los programas que toman información de fecha y hora de los usuarios y necesitan convertir esta entrada de usuario en valores DateTime, framework proporciona compatibilidad con el análisis de cadenas con formato de maneras específicas. En general, los métodos DateTime.Parse y ParseExact son útiles para convertir cadenas que contienen fechas y horas en valores DateTime. Por el contrario, los métodos ToString, ToLongDateString, ToLongTimeString, ToShortDateString y ToShortTimeString son útiles para representar valores DateTime en cadenas legibles por humanos.

Dos problemas principales que afectan al análisis son la referencia cultural y la cadena de formato. Las preguntas más frecuentes (P+F) de DateTime tratan los problemas básicos sobre la referencia cultural, por lo que aquí nos centraremos en los procedimientos recomendados de cadena de formato que afectan al análisis de DateTime.

Las cadenas de formato recomendadas para convertir DateTime en cadenas son:

'aaaa'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff'Z' —Para los valores de UCT

'aaaa'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff'zzz' —Para los valores locales

'yyyy'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff' —Para valores de tiempo abstractos

Estos son los valores de cadena de formato que se pasarían al método DateTime.ToString si desea obtener la salida compatible con la especificación de tipo DateTime XML. Las comillas aseguran que la configuración de fecha y hora local en el equipo no invalide las opciones de formato. Si necesita especificar diseños diferentes, puede pasar otras cadenas de formato para una funcionalidad de representación de fechas bastante flexible, pero debe tener cuidado de usar solo la notación Z para representar cadenas de valores UCT y usar la notación zzz para los valores de hora local.

El análisis de cadenas y su conversión en valores DateTime se pueden realizar con los métodos DateTime.Parse y ParseExact. Para la mayoría de nosotros, Parse es suficiente, ya que ParseExact requiere que proporcione su propia instancia de objeto formateador . El análisis es bastante capaz y flexible, y puede convertir con precisión la mayoría de las cadenas que contienen fechas y horas.

Por último, es importante llamar siempre a los métodos Parse y ToString solo después de establecer cultureInfo del subproceso en CultureInfo.InvariantCulture.

Consideración futura

Una cosa que no se puede hacer fácilmente en la actualidad con DateTime.ToString es dar formato a un valor DateTime en una zona horaria arbitraria. Esta característica se está considerando para futuras implementaciones de .NET Framework. Si necesita poder determinar que la cadena "12:00:00 EST" equivale a "11:00:00 EDT", tendrá que controlar la conversión y la comparación usted mismo.

Problemas con el método DateTime.Now()

Hay varios problemas al tratar con el método denominado Now. Para los desarrolladores de Visual Basic que leen esto, esto también se aplica a la función Ahora de Visual Basic. Los desarrolladores que usan regularmente el método Now saben que se usa normalmente para obtener la hora actual. El valor devuelto por el método Now está en el contexto de zona horaria de la máquina actual y no se puede tratar como un valor inmutable. Una práctica habitual es convertir las horas que se van a almacenar o enviar entre máquinas a tiempo universal (UCT).

Cuando el horario de verano es una posibilidad, hay una práctica de codificación que debe evitar. Tenga en cuenta el código siguiente que puede presentar un error difícil de detectar:

Dim timeval As DateTime
timeval = DateTime.Now().ToUniversalTime()  

El valor resultante de la ejecución de este código estará desactivado en una hora si se llama durante la hora adicional que se produce durante el cambio de horario de verano en la caída. (Esto solo se aplica a las máquinas que se encuentran en zonas horarias que practica el horario de verano). Dado que la hora adicional entra en ese lugar en el que el mismo valor, como las 1:10:00 a. m., se produce dos veces en esa mañana, el valor devuelto puede no coincidir con el valor deseado.

Para corregir esto, un procedimiento recomendado es llamar a DateTime.UtcNow() en lugar de llamar a DateTime.Now y, a continuación, convertir a la hora universal.

Dim timeval As DateTime
timeval = DateTime.UtcNow()  

Este código siempre tendrá la perspectiva adecuada de 24 horas del día y, a continuación, se puede convertir de forma segura a la hora local.

Procedimiento recomendado n.º 8

Cuando esté codificando y desea almacenar la hora actual representada como hora universal, evite llamar a DateTime.Now() seguida de una conversión a la hora universal. En su lugar, llame directamente a la función DateTime.UtcNow.

Advertencia: Si va a serializar una clase que contiene un valor DateTime, asegúrese de que el valor que se serializa no representa la hora universal. La serialización XML no admitirá la serialización UCT hasta la versión de Whidbey de Visual Studio.

Un par de extras poco conocidos

A veces, al empezar a profundizar en una parte de una API, se encuentra una gema oculta, algo que le ayuda a lograr un objetivo, pero que, si no se le dice, no se descubre en sus viajes diarios. El tipo de valor DateTime de .NET tiene varias gemas de este tipo que pueden ayudarle a lograr un uso más coherente de la hora universal.

La primera es la enumeración DateTimeStyles que se encuentra en el espacio de nombres System.Globalization . La enumeración controla los comportamientos de las funciones DateTime.Parse() y ParseExact que se usan para convertir la entrada especificada por el usuario y otras formas de representaciones de cadena de entrada en valores DateTime.

En la tabla siguiente se resaltan algunas de las características que habilita la enumeración DateTimeStyles.

Constante de enumeración Propósito Advertencias
AdjustToUniversal Cuando se pasa como parte de un método Parse o ParseExact, esta marca hace que el valor devuelto sea hora universal. La documentación es ambigua, pero funciona con Parse y ParseExact.
NoCurrentDateDefault Suprime la suposición de que las cadenas que se analizan sin componentes de fecha tendrán un valor DateTime devuelto que es la hora de la fecha actual. Si se usa esta opción, el valor DateTime devuelto es la hora especificada en la fecha gregoriana del 1 de enero del año 1.
AllowWhiteSpaces

AllowTrailingWhite

AllowLeadingWhite

AllowInnerWhite

Estas opciones permiten la tolerancia a los espacios en blanco agregados delante, detrás y en medio de las cadenas de fecha que se analizan. None

Otras funciones de compatibilidad interesantes se encuentran en la clase System.Timezone . Asegúrese de comprobarlos si desea detectar si el horario de verano afectará a un valor DateTime o si desea determinar mediante programación el desplazamiento de zona horaria actual para la máquina local.

Conclusión

La clase DateTime de .NET Framework proporciona una interfaz completa para escribir programas que se ocupan del tiempo. Comprender los matices de tratar con la clase va más allá de lo que puede obtener de IntelliSense®. Aquí hemos tratado los procedimientos recomendados para codificar y probar programas que tratan con fechas y horas. ¡ Feliz codificación!