Fecha y hora frente a DateTimeOffset
¿ Cuál es la diferencia entre a DateTime
y a DateTimeOffset
y cuándo se debe utilizar?
Actualmente, tenemos una forma estándar de tratar con .NET DateTime
teniendo en cuenta la zona horaria: cada vez que producimos un archivo, DateTime
lo hacemos en UTC (por ejemplo, usandoDateTime.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
cómo captura la hora local y UTC en el objeto mismo.
DateTimeOffset
es una representación del tiempo instantáneo (también conocido como tiempo absoluto ). Con esto me refiero a un momento en el tiempo que es universal para todos (sin tener en cuenta los segundos intercalares ni los efectos relativistas de la dilatación del tiempo ). Otra forma de representar el tiempo instantáneo es con un DateTime
dónde .Kind
está DateTimeKind.Utc
.
Esto es distinto del tiempo del calendario (también conocido como tiempo civil ), que es una posición en el calendario de alguien, y existen muchos calendarios diferentes en todo el mundo. A estos calendarios los llamamos zonas horarias . La hora del calendario está representada por un DateTime
dónde .Kind
es DateTimeKind.Unspecified
o DateTimeKind.Local
. Y .Local
solo tiene sentido en escenarios en los que se tiene una comprensión implícita de dónde está ubicada la computadora que utiliza el resultado. (Por ejemplo, la estación de trabajo de un usuario)
Entonces, ¿por qué DateTimeOffset
en lugar de UTC DateTime
? Se trata de perspectiva. Usemos una analogía: haremos como si fuéramos fotógrafos.
Imagínese que está parado en la línea de tiempo de un calendario, apuntando con una cámara a una persona en la línea de tiempo instantánea colocada frente a usted. Usted alinea 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 mano firme, por lo que tu cámara tiembla).
La persona parada en la foto vería el ángulo desde el que vino la cámara. Si otros estuvieran tomando fotografías, podrían ser desde diferentes ángulos. Esto es lo que representa la Offset
parte del DateTimeOffset
.
Entonces, si etiqueta su cámara como "Hora del Este", a veces apuntará desde -5 y otras veces apuntará desde -4. Hay cámaras en todo el mundo, todas etiquetadas con 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 encima de él), por lo que conocer el desplazamiento no es suficiente para determinar con qué zona horaria está relacionada la hora.
¿Y qué pasa con UTC? Bueno, es la única cámara que garantiza un manejo firme. Está sobre un trípode, firmemente anclado al suelo. No irá a ninguna parte. A su ángulo de perspectiva lo llamamos desplazamiento cero.
Entonces, ¿qué nos dice esta analogía? Proporciona algunas pautas intuitivas.
Si estás representando el tiempo relativo a algún lugar en particular, represéntalo en tiempo calendario con un
DateTime
. Sólo asegúrese de no confundir nunca un calendario con otro.Unspecified
debería ser tu suposición.Local
sólo es útil viniendo deDateTime.Now
. Por ejemplo, puedo obtenerloDateTime.Now
y guardarlo en una base de datos, pero cuando lo recupero, debo asumir que esUnspecified
. No puedo confiar en que mi calendario local sea el mismo calendario del que fue tomado originalmente.Si siempre debes estar seguro del momento, asegúrate de representar el tiempo instantáneo. Úselo
DateTimeOffset
para aplicarlo o use UTCDateTime
por convención.Si necesita realizar un seguimiento de un momento instantáneo, pero también desea saber "¿Qué hora pensó el usuario que era en su calendario local?" - entonces debes usar un
DateTimeOffset
. Esto es muy importante, por ejemplo, para los sistemas de cronometraje, tanto por motivos técnicos como legales.Si alguna vez necesita modificar una grabación anterior
DateTimeOffset
, no tiene suficiente información solo en la compensación para garantizar que la nueva compensación siga 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 fotografía incluso si la posición ha cambiado).También cabe señalar que Noda Time tiene una representación llamada
ZonedDateTime
para esto, mientras que la biblioteca de clases base .Net no tiene nada similar. Necesitaría almacenar tanto aDateTimeOffset
como aTimeZoneInfo.Id
valor.Ocasionalmente, querrás representar una hora del calendario que sea local para "quien la esté mirando". Por ejemplo, a la hora de 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 el último momento). Entonces, en estas situaciones, asegúrese de comprender cómo limitar el "¿quién pregunta?" preguntas a una única zona horaria, o tratar de traducirlas de nuevo a tiempo instantáneo, según corresponda.
Aquí hay algunos otros detalles DateTimeOffset
que respaldan esta analogía y algunos consejos para mantenerla clara:
Si compara dos
DateTimeOffset
valores, primero se normalizan a desplazamiento cero antes de comparar. En otras palabras,2012-01-01T00:00:00+00:00
y2012-01-01T02:00:00+02:00
se refieren al mismo momento instantáneo, y por tanto son equivalentes.Si está realizando alguna prueba unitaria y necesita estar seguro del desplazamiento, pruebe tanto el
DateTimeOffset
valor como la.Offset
propiedad por separado.Hay una conversión implícita unidireccional integrada en el marco .Net que le permite pasar
DateTime
a cualquierDateTimeOffset
parámetro o variable. Al hacerlo, lo.Kind
importante . Si pasa un tipo UTC, se trasladará con un desplazamiento cero, pero si pasa.Local
o.Unspecified
, se asumirá que es local . Básicamente, el marco 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 simplemente usaré el calendario local". Este es un gran problema si carga un no especificadoDateTime
en una computadora con una zona horaria diferente. (En mi humilde opinión, eso debería generar una excepción, pero no es así).
Enchufe descarado:
Muchas personas me han dicho 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, "El contexto importa", en el clip titulado "Calendar Time vs. Instantaneous Time".
De Microsoft:
Estos usos de los valores DateTimeOffset son mucho más comunes que los de los valores 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
Lo usamos DateTimeOffset
para casi todo, ya que nuestra aplicación trata con momentos particulares en el tiempo (por ejemplo, cuando se creó/actualizó un registro). DATETIMEOFFSET
Como nota al margen, también lo utilizamos en SQL Server 2008.
Lo veo DateTime
útil cuando quieres tratar solo con fechas, solo con horas o tratar cualquiera de las dos en un sentido genérico. Por ejemplo, si tiene una alarma que desea que suene todos los días a las 7 a. m., puede guardarla en un archivo DateTime
porque desea que suene a las 7 a. m. independientemente del horario de verano. Pero si desea representar el historial de ocurrencias de alarmas, usaría .DateTimeKind
Unspecified
DateTimeOffset
Tenga cuidado al utilizar una combinación de tipos DateTimeOffset
y DateTime
especialmente al asignarlos y compararlos. Además, solo compare DateTime
instancias que sean iguales DateTimeKind
porque DateTime
ignora el desplazamiento de la zona horaria al comparar.
DateTime es capaz de almacenar sólo 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 trabajar con UTC. Lo cual en sí mismo es una buena idea por cierto.
Este fragmento de código de Microsoft lo 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