.net datetime dst

.net - DateTime-Extraño comportamiento de ahorro de luz diurna



dst (2)

Mi zona horaria local es (UTC + 10: 00) Canberra, Melbourne, Sydney

Sat 31-Mar-2012 15:59 UTC = Sun 01-Apr-2012 02:59 +11: 00
Sat 31-Mar-2012 16:00 UTC = Sun 01-Apr-2012 02:00 +10: 00

El horario de verano termina a las 3 am del primer domingo de abril y el reloj retrocede 1 hora.

Dado el siguiente código ....

DateTime dt1 = DateTime.Parse("31-Mar-2012 15:59", CultureInfo.CurrentCulture, DateTimeStyles.AssumeUniversal); DateTime dt2 = DateTime.Parse("31-Mar-2012 15:59", CultureInfo.CurrentCulture, DateTimeStyles.AssumeUniversal).AddMinutes(1); DateTime dt3 = DateTime.Parse("31-Mar-2012 16:00", CultureInfo.CurrentCulture, DateTimeStyles.AssumeUniversal); Console.WriteLine("{0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt1); Console.WriteLine("{0:yyyy-MMM-dd HH:mm:ss.ffff K} ({1}) = {2:yyyy-MMM-dd HH:mm:ss.ffff K} ({3})", dt2, dt2.Kind, dt3, dt3.Kind); Console.WriteLine("{0} : {1} : {2}", dt1.ToUniversalTime().Hour, dt2.ToUniversalTime().Hour, dt3.ToUniversalTime().Hour);

Obtengo la siguiente salida

2012-Abr-01 02: 59: 00.0000 +11: 00
2012-Abr-01 03: 00: 00.0000 +10: 00 (Local) = 2012-Abr-01 02: 00: 00.0000 +10: 00 (Local)
15: 17: 16

Al agregar 1 minuto a la fecha y hora original, la hora local es a las 3AM, pero también establece el desplazamiento en +10 horas. Al agregar 1 minuto a la fecha UTC y el análisis, la hora local se ajusta correctamente a las 2 AM con un desplazamiento de +10 UTC.

Repitiendo con

DateTime dt1 = new DateTime(2012, 03, 31, 15, 59, 0, DateTimeKind.Utc); DateTime dt2 = new DateTime(2012, 03, 31, 15, 59, 0, DateTimeKind.Utc).AddMinutes(1); DateTime dt3 = new DateTime(2012, 03, 31, 16, 0, 0, DateTimeKind.Utc);

o

DateTime dt1 = DateTime.Parse("31-Mar-2012 15:59", CultureInfo.CurrentCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); DateTime dt2 = DateTime.Parse("31-Mar-2012 15:59", CultureInfo.CurrentCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal).AddMinutes(1); DateTime dt3 = DateTime.Parse("31-Mar-2012 16:00", CultureInfo.CurrentCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);

da

2012-Mar-31 15: 59: 00.0000 Z
2012-Mar-31 16: 00: 00.0000 Z (Utc) = 2012-Mar-31 16: 00: 00.0000 Z (Utc)
15: 16: 16

como se esperaba

Repitiendo de nuevo con

DateTime dt1 = new DateTime(2012, 03, 31, 15, 59, 0, DateTimeKind.Utc).ToLocalTime(); DateTime dt2 = new DateTime(2012, 03, 31, 15, 59, 0, DateTimeKind.Utc).ToLocalTime().AddMinutes(1); DateTime dt3 = new DateTime(2012, 03, 31, 16, 0, 0, DateTimeKind.Utc).ToLocalTime();

da el original

2012-Abr-01 02: 59: 00.0000 +11: 00
2012-Abr-01 03: 00: 00.0000 +10: 00 (Local) = 2012-Abr-01 02: 00: 00.0000 +10: 00 (Local)
15: 17: 16

¿Alguien puede explicar esto?

Indecentemente, si uso TimeZoneInfo para convertir de UTC a AUS Eastern Standard Time, obtengo la hora correcta, pero pierdo la información de desplazamiento en la instancia de DateTime como DateTime.Kind == DateTimeKind.Unspecified

== Escenario adicional a destacar

Esto es solo una simple adición de tiempo, comenzando con una fecha UTC NO ambigua, 1 minuto antes de que termine el horario de verano.

DateTime dt1 = new DateTime(2012, 03, 31, 15, 59, 0, DateTimeKind.Utc); DateTime dt2 = new DateTime(2012, 03, 31, 15, 59, 0, DateTimeKind.Utc).ToLocalTime(); Console.WriteLine("Original in UTC : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt1); Console.WriteLine("Original in Local : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt1.ToLocalTime()); Console.WriteLine("+ 1 Minute in Local : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt1.AddMinutes(1).ToLocalTime()); Console.WriteLine("+ 1 Minute in UTC : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt1.AddMinutes(1)); Console.WriteLine("====================================================="); Console.WriteLine("Original in UTC : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt2.ToUniversalTime()); Console.WriteLine("Original in Local : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt2); Console.WriteLine("+ 1 Minute in Local : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt2.AddMinutes(1)); Console.WriteLine("+ 1 Minute in UTC : {0:yyyy-MMM-dd HH:mm:ss.ffff K}", dt2.AddMinutes(1).ToUniversalTime());

da

Original en UTC: 2012-Mar-31 15: 59: 00.0000 Z
Original en Local: 2012-Abr-01 02: 59: 00.0000 +11: 00
+ 1 minuto en Local: 2012-Abr-01 02 : 00: 00.0000 +10: 00
+ 1 minuto en UTC: 2012-Mar-31 16 : 00: 00.0000 Z

================================================== ===

Original en UTC: 2012-Mar-31 15: 59: 00.0000 Z
Original en Local: 2012-Abr-01 02: 59: 00.0000 +11: 00
+ 1 minuto en Local: 2012-Abr-01 03 : 00: 00.0000 +10: 00
+ 1 minuto en UTC: 2012-Mar-31 17 : 00: 00.0000 Z


Creo que el problema es en términos de cuándo se realizan las conversiones.

Se analiza asumiendo el tiempo universal, pero luego se convierte implícitamente a un tipo "local", con un valor de 2:59:59. Cuando le pide a ese valor "local" que agregue un minuto, solo agrega un minuto al valor local, sin considerar la zona horaria. Cuando luego imprime el desplazamiento, el sistema está tratando de calcular el desplazamiento a la hora local de 3 am ... que es +10.

Así que efectivamente tienes:

  • Analizar el paso 1: tratar la cadena como universal (15:59 UTC)
  • Analizar el paso 2: convertir el resultado a local (2:59 local)
  • Adición: en la hora local, no se aplican valores de zona horaria (3:00 local)
  • Formato paso 1: se solicita el desplazamiento, por lo que calcule a qué se corresponde la hora local (17:00 UTC)
  • Formato del paso 2: calcular el desplazamiento como diferencia entre local y universal (+10)

Sí, todo es un poco doloroso: DateTime es doloroso en general , que es la razón principal por la que escribo Noda Time , donde hay tipos separados para "fecha / hora en una zona" frente a "fecha / hora local" (o " fecha local "o" hora local ", y es obvio que está utilizando en cualquier punto.

No me queda claro lo que realmente intentas lograr aquí. Si puedes ser más específico, puedo mostrarte lo que harías en Noda Time, aunque puede haber algunas ambigüedades inherentes (conversiones de fecha / hora locales a " zonificado "fecha / hora puede tener 0, 1 o 2 resultados).

EDITAR: Si el objetivo es simplemente recordar la zona horaria así como el instante, en Noda Time querría ZonedDateTime , así:

using System; using NodaTime; class Program { static void Main(string[] args) { var zone = DateTimeZone.ForId("Australia/Melbourne"); ZonedDateTime start = Instant.FromUtc(2012, 3, 31, 15, 59, 0) .InZone(zone); ZonedDateTime end = start + Duration.FromMinutes(1); Console.WriteLine("{0} ({1})", start.LocalDateTime, start.Offset); Console.WriteLine("{0} ({1})", end.LocalDateTime, end.Offset); } }

Consulte las notas sobre la aritmética del calendario para obtener más información sobre esto.


Mi forma de lidiar con esto es tratar a DateTime un poco como a Flotadores: requieren un manejo especial para cuando los estás manipulando y cuando los estás mostrando al usuario. Yo uso una pequeña biblioteca que escribí para envolverlos:

https://github.com/b9chris/TimeZoneInfoLib.Net

Y siempre trátelos como UTC + TimeZoneInfo. De esa manera, puede hacer todos los cálculos típicos que haría normalmente, operando únicamente de UTC a UTC, y solo tratar con DateTimes locales en el último paso de mostrarlos al usuario en un formato agradable. Otra ventaja de esta estructura es que puede mostrar con mayor precisión una zona horaria limpia al usuario en un formato al que están acostumbrados, en lugar de buscar en la clase TimeZoneInfo cada vez.