DateTime vs DateTimeOffset

743

Actualmente, tenemos una forma estándar de tratar con .NET DateTime's de una manera consciente de TimeZone: cada vez que producimos una DateTimelo hacemos en UTC (por ejemplo, usando DateTime.UtcNow), y cada vez que mostramos una, volvemos de UTC a la hora local del usuario .

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

David Reis
fuente
3
Hay algunas respuestas geniales a continuación. Pero todavía me pregunto qué, si acaso, podría haberlo convencido de comenzar a usar DateTimeOffset.
HappyNomad el
Cuando se trata de almacenamiento, stackoverflow.com/questions/4715620/… también es interesante.
Dejan

Respuestas:

1168

DateTimeOffsetes una representación del 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 tener en cuenta los segundos bisiestos o los efectos relativistas de la dilatación del tiempo ). Otra forma de representar el tiempo instantáneo es con un lugar DateTimedonde .Kindestá DateTimeKind.Utc.

Esto es distinto del tiempo de 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 . El tiempo del calendario está representado por un lugar DateTimedonde .Kindestá DateTimeKind.Unspecified, o DateTimeKind.Local. Y .Localsolo es significativo 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é en DateTimeOffsetlugar de un UTC DateTime? Se trata de perspectiva. Usemos una analogía: fingiremos ser fotógrafos.

Imagine que está parado en una línea de tiempo del calendario, apuntando con una cámara a una persona en la línea de tiempo instantánea que se encuentra 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 tienes una mano firme, por lo que tu cámara está temblorosa).

La persona de pie en la foto vería el ángulo desde el cual proviene su cámara. Si otros tomaran fotos, podrían ser desde diferentes ángulos. Esto es lo que representa la Offsetparte de DateTimeOffset.

Por lo tanto, si etiqueta su cámara como "Hora del Este", a veces apunta desde -5 y otras desde -4. Hay cámaras en todo el mundo, todas etiquetadas como cosas diferentes, y todas apuntando a la misma línea de tiempo instantánea desde diferentes ángulos. Algunos de ellos están uno al lado del otro (o uno encima del otro), por lo que conocer el desplazamiento no es suficiente para determinar con qué zona horaria está relacionada la hora.

¿Y qué hay de UTC? Bueno, es la única cámara que garantiza tener una mano firme. Está en un trípode, firmemente anclado en el suelo. No va a ninguna parte. Llamamos a su ángulo de perspectiva el desplazamiento cero.

Tiempo instantáneo vs visualización del tiempo calendario

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

  • Si está representando el tiempo en relación con algún lugar en particular, hágalo en tiempo calendario con a DateTime. Solo asegúrese de no confundir nunca un calendario con otro. UnspecifiedDebería ser su suposición. Localsolo es útil viniendo de DateTime.Now. Por ejemplo, podría obtenerlo DateTime.Nowy guardarlo en una base de datos, pero cuando lo recupere, debo asumir que es así Unspecified. No puedo confiar en que mi calendario local sea el mismo calendario del que fue tomado originalmente.

  • Si siempre debe estar seguro del momento, asegúrese de representar el tiempo instantáneo. Úselo DateTimeOffsetpara aplicarlo, o use UTC DateTimepor convención.

  • Si necesita realizar un seguimiento de 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 a DateTimeOffset. Esto es muy importante para los sistemas de cronometraje, por ejemplo, tanto por cuestiones técnicas como legales.

  • Si alguna vez necesita modificar un registro anterior DateTimeOffset, no tiene suficiente información solo en el desplazamiento para garantizar que el nuevo desplazamiento aún sea relevante para el usuario. Debe también almacenar un identificador de zona horaria (piensa - Necesito el nombre de esa cámara para que pueda tomar una nueva imagen, incluso si la posición ha cambiado).

    También debe señalarse que Noda Time tiene una representación llamada ZonedDateTimepara esto, mientras que la biblioteca de clase base .Net no tiene nada similar. Tendría que almacenar tanto un DateTimeOffsetcomo un TimeZoneInfo.Idvalor.

  • Ocasionalmente, querrá representar un tiempo de calendario que sea local para "quien lo esté mirando". Por ejemplo, al definir lo que significa hoy . Hoy siempre es de medianoche a medianoche, pero estos representan un número casi infinito 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 expresar las compensaciones hasta la marca). En estas situaciones, asegúrese de comprender cómo limitar el "¿quién pregunta?" pregunta a una sola zona horaria, o trata de traducirlos nuevamente al tiempo instantáneo según corresponda.

Aquí hay algunos otros pequeños detalles sobre DateTimeOffseteso que respaldan esta analogía, y algunos consejos para mantenerla recta:

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

  • Si está haciendo alguna prueba unitaria y necesita asegurarse del desplazamiento, pruebe tanto el DateTimeOffsetvalor como la .Offsetpropiedad por separado.

  • Hay una conversión implícita unidireccional integrada en el marco .Net que le permite pasar DateTimea cualquier DateTimeOffsetparámetro o variable. Al hacerlo, los .Kindasuntos . Si pasa un tipo GMT, llevará con un desplazamiento cero, pero si pasa o bien .Localo .Unspecified, asumirá ser locales . El marco básicamente dice: "Bueno, me pediste que convirtiera el tiempo del calendario a tiempo instantáneo, pero no tengo idea de dónde vino esto, así que solo voy a usar el calendario local". Este es un gran problema si carga un no especificado DateTimeen una computadora con una zona horaria diferente. (En mi humilde opinión, eso debería arrojar una excepción, pero no lo hace).

Enchufe desvergonzado:

Muchas personas han compartido conmigo que encuentran esta analogía extremadamente valiosa, por lo que la incluí en mi curso Pluralsight, Fundamentos de fecha y hora . Encontrará un tutorial 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".

Matt Johnson-Pint
fuente
44
@ZackJannsen Si tiene un DateTimeOffseten C #, entonces debe persistirlo en un DATETIMEOFFSETen SQL Server. DATETIME2o simplemente DATETIME(dependiendo del rango requerido) están bien para DateTimevalores regulares . Sí, puede resolver una hora local desde cualquier emparejamiento de zona horaria + dto o utc. La diferencia es: ¿siempre desea calcular las reglas con cada resolución, o desea calcularlas previamente? En muchos casos (a veces por cuestiones legales) un DTO es una mejor opción.
Matt Johnson-Pint
3
@ZackJannsen Para la segunda parte de su pregunta, recomendaría hacer tanto como sea posible del lado del servidor. Javascript no es tan bueno para el cálculo de la zona horaria. Si debe hacerlo, use una de estas bibliotecas . Pero el lado del servidor es el mejor. Si tiene otras preguntas más detalladas, comience una nueva pregunta SO para ellas y le responderé si puedo. Gracias.
Matt Johnson-Pint
44
@JoaoLeme: eso depende de dónde lo obtuviste. Tiene razón en que si dice DateTimeOffset.Nowen el servidor, obtendrá el desplazamiento del servidor. El punto es que el DateTimeOffsettipo puede retener ese desplazamiento. Puede hacer eso con la misma facilidad en el cliente, enviarlo al servidor y luego su servidor conocerá el desplazamiento del cliente.
Matt Johnson-Pint el
8
Realmente me encanta la analogía de la cámara.
Sachin Kainth
2
Si eso es correcto. Excepto que el DTO se almacena como el par (hora local, desplazamiento), no el par (hora utc, desplazamiento). En otras palabras, el desplazamiento desde UTC ya se refleja en la hora local. Para volver a convertir a utc, invierta el signo del desplazamiento y aplíquelo a la hora local.
Matt Johnson-Pint
328

De Microsoft:

Estos usos para los valores de DateTimeOffset son mucho más comunes que los de los valores de DateTime. Como resultado, DateTimeOffset debe considerarse el tipo de fecha y hora predeterminado para el desarrollo de aplicaciones.

fuente: "Elegir entre DateTime, DateTimeOffset, TimeSpan y TimeZoneInfo" , MSDN

Usamos DateTimeOffsetpara casi todo, ya que nuestra aplicación se ocupa de puntos particulares en el tiempo (por ejemplo, cuando se creó / actualizó un registro). Como nota al margen, también utilizamos DATETIMEOFFSETen SQL Server 2008.

Veo DateTimeque es útil cuando quieres tratar solo con fechas, solo horas o tratar en un sentido genérico. Por ejemplo, si usted tiene una alarma que desea salir todos los días a las 7 am, podría almacenar que en una DateTimeutilización de una DateTimeKindde Unspecifiedporque usted quiere que vaya a las 7 am sin importar el horario de verano. Pero si desea representar el historial de eventos de alarma, lo usaría DateTimeOffset.

Tenga precaución cuando use una combinación de DateTimeOffsety DateTimeespecialmente cuando asigne y compare entre los tipos. Además, solo compare DateTimeinstancias que sean iguales DateTimeKindporque DateTimeignora el desplazamiento de la zona horaria al comparar.

Arcilla
fuente
146
La respuesta aceptada es demasiado larga y la analogía es tensa, esta es una respuesta OMI mucho mejor y más concisa.
Nexus dice
10
Solo diré que también me gusta esta respuesta y que voté positivamente. Aunque en la última parte, incluso garantizar que Kindsean iguales, la comparación podría ser un error. Si ambos lados lo tienen DateTimeKind.Unspecified, realmente no saben que provienen de la misma zona horaria. Si ambos lados lo están DateTimeKind.Local, la mayoría de las comparaciones estarán bien, pero aún podría tener errores si un lado es ambiguo en la zona horaria local. Realmente solo las DateTimeKind.Utccomparaciones son infalibles, y sí, DateTimeOffsetgeneralmente se prefiere. (¡Salud!)
Matt Johnson-Pint
1
+1 Añadiría a esto: el DataType que elijas debe reflejar tu intención. No use DateTimeOffset en todas partes, solo porque sí. Si la compensación es importante para sus cálculos y para leer desde / persistir a la base de datos, use DateTimeOffset. Si no importa, entonces use DateTime, para que entienda (simplemente mirando el DataType) que el Offset no debería tener ninguna relación y Times debería permanecer en relación con la Localidad del Servidor / Máquina en la que se está ejecutando su Código C #.
MikeTeeVee
"Pero si desea representar el historial de ocurrencias de alarmas, usaría DateTimeOffset". ¿Te gustaría explicar por qué crees que es así? Podría completar eso leyendo toda la información en esta página, pero tal vez usted también podría proporcionar dicha información de una manera fácil de leer. Esta es una respuesta muy legible.
Barrosy el
77

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

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

Hans Passant
fuente
33

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

Probablemente hay otros, pero el ejemplo anterior es en realidad uno con el que me he encontrado en el pasado (esto fue antes de la adición DateTimeOffsetal BCL; mi solución en ese momento era almacenar explícitamente la hora en la zona horaria local y guardar la información de la zona horaria junto a ella: básicamente lo que DateTimeOffsethace internamente).

Dean Harding
fuente
12
DateTimeOffset no soluciona el problema del horario de verano
JarrettV
2
El uso de la clase TimeZoneInfo lleva reglas para el horario de verano. si está en .net 3.5 o posterior, use las clases TimeZone o TimeZoneInfo para manejar las fechas que deben manejar el horario de verano junto con el desplazamiento de la zona horaria.
Zack Jannsen
1
Sí, un buen ejemplo de una excepción (la aplicación de alarma), pero cuando la hora es más importante que la fecha, realmente debe almacenarla por separado en su estructura de datos de programación para la aplicación, es decir, tipo de ocurrencia = Diario y hora = 09:00. El punto aquí es que el desarrollador necesita saber qué tipo de fecha están grabando, calculando o presentando a los usuarios. Especialmente las aplicaciones tienden a ser más globales ahora que tenemos Internet como estándar y grandes tiendas de aplicaciones para las que escribir software. Como nodo lateral, también me gustaría ver a Microsoft agregar una estructura separada de Fecha y Hora.
Tony Wall
3
Resumiendo los comentarios de Jarrett y Zack: Parece que DateTimeOffset solo no manejará el problema de DST, pero el uso de DateTimeOffset junto con TimeZoneInfo lo manejará. Esto no es diferente de DateTime donde kind es Utc. En ambos casos, debo conocer la zona horaria (no solo el desplazamiento) del calendario para el que estoy proyectando el momento. (Podría almacenarlo en el perfil de un usuario u obtenerlo del cliente (por ejemplo, Windows) si es posible). ¿Suena bien?
Jeremy Cook, el
"Hay algunos lugares donde DateTimeOffset tiene sentido". --- Podría decirse que a menudo tiene sentido que no.
Ronnie Overby
23

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

Aunque DateTime distingue entre UTC y Local, no hay absolutamente ningún desplazamiento explícito de zona horaria asociado con él. 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 una compensación explícita en DateTime) usará la compensación de zona horaria del servidor.

Por ejemplo, si serializa un valor 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 su DateTime o cualquier compensación que utilizó para calcularlo ... es solo la compensación de zona horaria del servidor.

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

Triynko
fuente
14
con todas las respuestas anteriores, me pregunto por qué nadie se molestó en escribir su única frase que lo resume todoThe most important distinction is that DateTime does not store time zone information, while DateTimeOffset does.
Korayem
99
DateTimeOffset NO almacena información de zona horaria. El documento de MS titulado "Elegir entre DateTime, DateTimeOffset, TimeSpan y TimeZoneInfo" especifica lo siguiente: "Un valor de DateTimeOffset no está vinculado a una zona horaria en particular, pero puede originarse en cualquiera de una variedad de zonas horarias". Dicho esto, DateTimeOffset IS zona horaria CONSCIENTE, que contiene el desplazamiento de UTC, lo que hace toda la diferencia y es la razón por la cual es la clase predeterminada recomendada por MS cuando se trata de desarrollo de aplicaciones que trata con información de fecha. Si realmente le importa de qué zona horaria específica provienen los datos, debe conservarlos por separado
stonedauwg
1
Sí, pero como se ha demostrado en muchos lugares, + o - horas no dice nada sobre en qué zona horaria se encontraba y, en última instancia, es inútil. Dependiendo de lo que necesite hacer, puede almacenar una fecha y hora como Kind. Sin especificar y luego almacenar la identificación de su zona horaria y creo que en realidad está mejor.
Arwin
13

Este código de Microsoft explica todo:

// Find difference between Date.Now and Date.UtcNow
  date1 = DateTime.Now;
  date2 = DateTime.UtcNow;
  difference = date1 - date2;
  Console.WriteLine("{0} - {1} = {2}", date1, date2, difference);

  // Find difference between Now and UtcNow using DateTimeOffset
  dateOffset1 = DateTimeOffset.Now;
  dateOffset2 = DateTimeOffset.UtcNow;
  difference = dateOffset1 - dateOffset2;
  Console.WriteLine("{0} - {1} = {2}", 
                    dateOffset1, dateOffset2, difference);
  // If run in the Pacific Standard time zone on 4/2/2007, the example
  // displays the following output to the console:
  //    4/2/2007 7:23:57 PM - 4/3/2007 2:23:57 AM = -07:00:00
  //    4/2/2007 7:23:57 PM -07:00 - 4/3/2007 2:23:57 AM +00:00 = 00:00:00
Mojtaba
fuente
9

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

dekdev
fuente
7

Una diferencia importante es que DateTimeOffsetse puede usar junto con la TimeZoneInfoconversión a horas locales en zonas horarias distintas de 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.

Joe
fuente
3
@Bugeo Bugeo es cierto, pero existe un riesgo. Puede comparar dos DateTimes llamando primero a "ToUniversalTime" en cada uno. Si tiene exactamente un valor en la comparación que es DateTimeKind = Unspecified, su estrategia fallará. Este potencial de falla es una razón para considerar DateTimeOffset sobre DateTime cuando se requieren conversiones a la hora local.
Zack Jannsen
Como se indicó anteriormente, creo que en este escenario es mejor almacenar el TimeZoneId que usar DateTimeOffset, lo que finalmente no significa nada.
Arwin el
2

El único lado 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 que siga adelante y use DateTimeOffset y TimeZoneInfo debido a todos los beneficios, solo tenga cuidado al crear entidades que serán o pueden ser serializadas hacia o desde XML (todos los objetos comerciales entonces).

Tony Wall
fuente