DateTime versus DateTimeOffset

Actualmente, tenemos una forma estándar de tratar con .Net DateTimes en una forma conocida de TimeZone: cada vez que producimos un DateTime lo hacemos en UTC (por ejemplo, usando DateTime.UtcNow ), y cada vez que mostramos uno, convertimos de UTC a la hora local del usuario.

Eso funciona bien, pero he estado leyendo sobre DateTimeOffset y cómo captura la hora local y UTC en el objeto mismo. Entonces, la pregunta es, ¿cuáles serían las ventajas de utilizar DateTimeOffset frente a lo que ya hemos estado haciendo?

DateTimeOffset es una representación de tiempo instantáneo (también conocido como tiempo absoluto ). Con eso, me refiero a un momento en el tiempo que es universal para todos (sin contar los segundos intercalares , o los efectos relativistas de la dilatación del tiempo ). Otra forma de representar el tiempo instantáneo es con un DateTime donde .Kind es DateTimeKind.Utc .

Esto es distinto del tiempo calendario (también conocido como tiempo civil ), que es una posición en el calendario de alguien, y hay muchos calendarios diferentes en todo el mundo. Llamamos a estos calendarios zonas horarias . La hora del calendario está representada por un DateTime donde .Kind es DateTimeKind.Unspecified o DateTimeKind.Local . Y .Local solo tiene sentido en escenarios en los que tiene una comprensión implícita de dónde está posicionada la computadora que está utilizando el resultado. (Por ejemplo, la estación de trabajo de un usuario)

Entonces, ¿por qué DateTimeOffset lugar de un UTC DateTime ? Se trata de perspectiva. Usemos una analogía: pretendemos ser fotógrafos.

Imagine que está de pie en una línea de tiempo del calendario, apuntando una cámara a una persona en la línea de tiempo instantánea dispuesta frente a usted. Alinee su cámara de acuerdo con las reglas de su zona horaria, que cambian periódicamente debido al horario de verano o debido a otros cambios en la definición legal de su zona horaria. (No tiene una mano firme, por lo que su cámara es inestable.)

La persona que está parada en la foto podría ver el ángulo desde el que salió su cámara. Si otros tomaran fotos, podrían ser desde diferentes angularjs. Esto es lo que representa la parte de Offset del DateTimeOffset .

Por lo tanto, si etiqueta su cámara como “Hora del Este”, a veces señala desde -5, y algunas veces señala desde -4. Hay cámaras en todo el mundo, todas etiquetan cosas diferentes, y todas apuntan a la misma línea de tiempo instantánea desde diferentes angularjs. Algunos de ellos están justo al lado (o encima de) el uno del otro, por lo que simplemente conocer el desplazamiento no es suficiente para determinar con qué zona horaria está relacionada la hora.

¿Y qué hay de UTC? Bueno, es la cámara que está garantizada para tener una mano firme. Está en un trípode, firmemente anclado en el suelo. No va a ir a ningún lado. Llamamos a su ángulo de perspectiva el desplazamiento cero.

Tiempo instantáneo vs Visualización del tiempo del calendario

Entonces, ¿qué nos dice esta analogía? Proporciona algunas pautas intuitivas.

  • Si representa tiempo relativo a algún lugar en particular, represéntelo en tiempo calendario con un DateTime . Solo asegúrate de no confundir nunca un calendario con otro. Unspecified debe ser su suposición. Local solo es útil desde DateTime.Now . Por ejemplo, podría obtener DateTime.Now y guardarlo en una base de datos, pero cuando lo recupere, debo suponer que no está Unspecified . No puedo confiar en que mi calendario local sea el mismo calendario del que se tomó originalmente.

  • Si siempre debe estar seguro del momento, asegúrese de representar el tiempo instantáneo. Use DateTimeOffset para aplicarlo, o use UTC DateTime por convención.

  • Si necesita rastrear un momento de tiempo instantáneo, pero también desea saber “¿A qué hora pensó el usuario que estaba en su calendario local?” – entonces debes usar DateTimeOffset . Esto es muy importante para los sistemas de control de tiempo, por ejemplo, tanto para cuestiones técnicas como legales.

  • Si alguna vez necesita modificar un DateTimeOffset previamente grabado, no tiene suficiente información solo en el desplazamiento para asegurarse de que el nuevo desplazamiento sigue siendo relevante para el usuario. También debe almacenar un identificador de zona horaria (piense: necesito el nombre de esa cámara para poder tomar una nueva foto, incluso si la posición ha cambiado).

    También se debe señalar que Noda Time tiene una representación llamada ZonedDateTime para esto, mientras que la biblioteca de clases base de .Net no tiene nada similar. DateTimeOffset almacenar tanto un valor DateTimeOffset como un valor TimeZoneInfo.Id .

  • Ocasionalmente, querrás representar un tiempo calendario que sea local para “quien lo esté mirando”. Por ejemplo, al definir lo que significa hoy . Hoy es siempre de medianoche a medianoche, pero representa una cantidad casi infinita de rangos superpuestos en la línea de tiempo instantánea. (En la práctica, tenemos un número finito de zonas horarias, pero puede express compensaciones hasta la marca) Entonces, en estas situaciones, asegúrese de entender cómo limitar el “¿quién pregunta?” pregunta hacia una zona horaria única, o trate de traducirlas a la hora instantánea según corresponda.

Aquí hay algunos otros pequeños detalles sobre DateTimeOffset que respaldan esta analogía, y algunos consejos para mantenerlo en línea:

  • Si compara dos valores DateTimeOffset , primero se normalizan a cero offset antes de comparar. En otras palabras, 2012-01-01T00:00:00+00:00 y 2012-01-01T02:00:00+02:00 refieren al mismo momento instantáneo, y por lo tanto son equivalentes.

  • Si está realizando una prueba unitaria y necesita estar seguro del desplazamiento, pruebe el valor DateTimeOffset y la propiedad .Offset separado.

  • Existe una conversión implícita unidireccional integrada en .Net Framework que le permite pasar un DateTime a cualquier parámetro o variable DateTimeOffset . Al hacerlo, el .Kind importa . Si pasa un tipo UTC, se transferirá con un desplazamiento cero, pero si pasa .Local o .Unspecified , supondrá que es local . El marco básicamente dice: “Bueno, me pediste que convirtiera el tiempo del calendario en tiempo instantáneo, pero no tengo idea de dónde vino esto, así que solo voy a usar el calendario local”. Esto es una gran sorpresa si carga un DateTime no especificado en una computadora con una zona horaria diferente. (En mi humilde opinión, eso debería arrojar una excepción, pero no es así).

Plug desvergonzado:

Muchas personas han compartido conmigo que consideran que esta analogía es extremadamente valiosa, así que la incluí en mi curso de Pluralsight, Fundamentos de Fecha y Hora . Encontrará un recorrido paso a paso de la analogía de la cámara en el segundo módulo, “Context Matters”, en el clip titulado “Calendar Time vs. Instant Time”.

De Microsoft:

Estos usos para los valores de DateTimeOffset son mucho más comunes que los de los valores de DateTime. Como resultado, DateTimeOffset se debe considerar el tipo de fecha y hora predeterminado para el desarrollo de la aplicación.

fuente: “Elegir entre DateTime, DateTimeOffset, TimeSpan y TimeZoneInfo” , MSDN

Utilizamos DateTimeOffset para casi todo, ya que nuestra aplicación trata puntos específicos en el tiempo (por ejemplo, cuando se creó / actualizó un registro). Como nota al margen, también utilizamos DATETIMEOFFSET en SQL Server 2008.

Veo que DateTime es útil cuando solo quieres tratar fechas, horarios o tratar en un sentido genérico. Por ejemplo, si tiene una alarma que desea apagar todos los días a las 7 a.m., puede almacenarla en un DateTime utilizando un DateTimeKind of Unspecified porque desea que se active a las 7 a.m., independientemente del horario de verano. Pero si desea representar el historial de ocurrencias de alarma, usaría DateTimeOffset .

Tenga cuidado al usar una mezcla de DateTimeOffset y DateTime especialmente al asignar y comparar entre los tipos. Además, solo compare las instancias DateTime que son las mismas DateTimeKind porque DateTime ignora el desplazamiento de la zona horaria al comparar.

DateTime es capaz de almacenar solo dos horarios distintos, la hora local y el UTC. La propiedad Kind indica cuál.

DateTimeOffset amplía esto al poder almacenar horas locales desde cualquier lugar del mundo. También almacena el desplazamiento entre esa hora local y UTC. Observe cómo DateTime no puede hacer esto a menos que agregue un miembro adicional a su clase para almacenar ese desplazamiento UTC. O solo trabaje con UTC. Lo cual en sí mismo es una buena idea por cierto.

Hay algunos lugares donde DateTimeOffset tiene sentido. Una es cuando se trata de eventos recurrentes y horario de verano. Digamos que quiero configurar una alarma para que suene a las 9 a.m. todos los días. Si utilizo la regla “almacenar como UTC, mostrar como hora local”, la alarma se activará en un momento diferente cuando esté en vigencia el horario de verano.

Probablemente haya otros, pero el ejemplo anterior es en realidad uno con el que me he encontrado en el pasado (esto fue antes de agregar DateTimeOffset al BCL; mi solución en ese momento era almacenar explícitamente la hora en la zona horaria local, y guarde la información de la zona horaria al costado: básicamente lo que hace DateTimeOffset internamente).

La distinción más importante es que DateTime no almacena información de zona horaria, mientras que DateTimeOffset sí lo hace.

Aunque DateTime distingue entre UTC y Local, no hay absolutamente ninguna compensación de zona horaria explícita asociada. Si realiza algún tipo de serialización o conversión, se utilizará la zona horaria del servidor. Incluso si crea manualmente una hora local agregando minutos para compensar una hora UTC, aún puede obtener un bit en el paso de serialización, porque (debido a la falta de un desplazamiento explícito en DateTime) usará el desplazamiento de la zona horaria del servidor.

Por ejemplo, si serializa un valor de DateTime con Kind = Local usando Json.Net y un formato de fecha ISO, obtendrá una cadena como 2015-08-05T07:00:00-04 . Tenga en cuenta que la última parte (-04) no tuvo nada que ver con DateTime ni con ninguna compensación que haya utilizado para calcularla … simplemente es la compensación de la zona horaria del servidor.

Mientras tanto, DateTimeOffset incluye explícitamente el desplazamiento. Puede que no incluya el nombre del huso horario, pero al menos incluye el desplazamiento, y si lo serializa, obtendrá el desplazamiento explícitamente incluido en su valor en lugar de la hora local del servidor.

La mayoría de las respuestas son buenas, pero pensé en agregar algunos más enlaces de MSDN para obtener más información

  • Una breve historia de DateTime – por Anthony Moore por el equipo de BCL
  • Elegir entre Datetime y DateTime Offset – por MSDN
  • No olvide SQL Server 2008 en adelante tiene un nuevo tipo de datos como DateTimeOffset
  • .NET Framework incluye los tipos DateTime , DateTimeOffset y TimeZoneInfo , todos los cuales se pueden usar para crear aplicaciones que funcionen con fechas y horas.
  • Realización de operaciones aritméticas con fechas y horas: MSDN

Una diferencia importante es que DateTimeOffset se puede usar junto con TimeZoneInfo para convertir a horas locales en zonas horarias distintas a la actual.

Esto es útil en una aplicación de servidor (por ejemplo, ASP.NET) a la que acceden los usuarios en diferentes zonas horarias.

El único aspecto negativo de DateTimeOffset que veo es que Microsoft “olvidó” (por diseño) admitirlo en su clase XmlSerializer. Pero desde entonces se ha agregado a la clase de utilidad XmlConvert.

XmlConvert.ToDateTimeOffset

XmlConvert.ToString

Digo adelante y uso DateTimeOffset y TimeZoneInfo debido a todos los beneficios, solo tenga cuidado al crear entidades que serán serializadas ao desde XML (todos los objetos comerciales en ese momento).