DateTime - Странное поведение летнего времени - PullRequest
18 голосов
/ 02 марта 2012

Мой местный часовой пояс (UTC + 10: 00) Канберра, Мельбурн, Сидней

Суббота, 31 марта 2012 года, 15:59 UTC = Вс, 1 апреля 2012 года, 02:59 +11: 00
. Суббота 31 марта 2012 года 16:00 UTC = Вс 01 апреля 2012 года 02:00 +10: 00

Летнее время заканчивается в 3 часа ночи в первое воскресенье апреля, а часы возвращаются на 1 час назад.

Учитывая следующий код ....

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);

Я получаю следующий вывод

2012-апр-01 02: 59: 00.0000 +11: 00
2012-Apr-01 03: 00: 00.0000 +10: 00 (местный) = 2012-Apr-01 02: 00: 00.0000 +10: 00 (местный)
15: 17: 16

Добавление 1 минуты к исходному времени делает местное время 3:00, но также устанавливает смещение на +10 часов. При добавлении 1 минуты к дате UTC и правильном разборе местное время устанавливается на 2 часа утра со смещением +10 UTC.

Повтор с

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);

или

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); 

1025 * дает *

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

как и ожидалось

Повторять снова с

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();

дает оригинал

2012-апр-01 02: 59: 00.0000 +11: 00
2012-Apr-01 03: 00: 00.0000 +10: 00 (местный) = 2012-Apr-01 02: 00: 00.0000 +10: 00 (местный)
15: 17: 16

Может кто-нибудь объяснить это?

Непристойно, если я использую TimeZoneInfo для преобразования из UTC в восточное стандартное время AUS, я получаю правильное время, но теряю информацию о смещении в экземпляре DateTime как DateTime.Kind == DateTimeKind.Unspecified

== Дополнительный сценарий для выделения

Это просто добавление временного интервала, начиная с недвусмысленной даты UTC, за 1 минуту до окончания перехода на летнее время.

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());  

дает

Оригинал в UTC: 2012-Mar-31 15: 59: 00.0000 Z
Оригинал на местном: 2012-апр-01 02: 59: 00.0000 +11: 00
+ 1 минута на местном: 2012-апр-01 02 : 00: 00.0000 +10: 00
+ 1 минута в UTC: 2012-март-31 16 : 00: 00.0000 Z

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

Оригинал в UTC: 2012-Mar-31 15: 59: 00.0000 Z
Оригинал на местном: 2012-апр-01 02: 59: 00.0000 +11: 00
+ 1 минута на местном: 2012-апр-01 03 : 00: 00.0000 +10: 00
+ 1 минута в UTC: 2012-март-31 17 : 00: 00.0000 Z

Ответы [ 2 ]

29 голосов
/ 02 марта 2012

Я считаю, что проблема заключается в , когда преобразования выполняются.

Вы анализируете, предполагая универсальное время, но затем неявно преобразуете в "локальный" вид - сзначение 2:59:59.Когда вы просите это «локальное» значение добавить минуту, это просто добавление минуты к локальному значению, без учета часового пояса.Когда вы затем распечатываете смещение, система пытается определить смещение по местному времени 3:00 ... что составляет + 10.

Так эффективно вы получаете:

  • Разобрать шаг 1: обработать строку как универсальную (15:59 UTC)
  • Разобрать шаг 2: преобразовать результат в локальное (2:59 местное)
  • Добавление: по местному времени, без времениприменяются значения зоны (3:00 по местному времени)
  • Шаг форматирования 1: запрашивается смещение, поэтому определите, к чему относится это местное время (17:00 UTC)
  • Шаг форматирования 2: вычислитесмещение как разница между локальным и универсальным (+10)

Да, все это немного болезненно - DateTime в целом болезненно , что является основной причиной, по которой я Noda Time , где есть отдельные типы для «дата / время в зоне» и «местная дата / время» (или «местная дата» или «местное время»), и очевидно, что вы 'использовать в любой момент.

Мне не ясно, чего вы на самом деле пытаетесь достичь здесь - если вы можете быть более конкретным, я могу показатьто, что вы будете делать в Noda Time, хотя могут быть некоторые присущие им неоднозначности (преобразования из локальной даты / времени в «зонированные» дату / время могут иметь 0, 1 или 2 результата).

РЕДАКТИРОВАТЬ: Еслицель состоит в том, чтобы просто запомнить часовой пояс, а также момент времени в Noda Time, который вы хотите ZonedDateTime, например:

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);
    }
}

См. примечания к календарной арифметике дляеще немного информации об этом.

0 голосов
/ 11 апреля 2013

Мой способ борьбы с этим заключается в том, чтобы относиться к DateTime немного как к Floats - они требуют особой обработки, когда вы манипулируете ими, а не когда показываете их пользователю. Я использую небольшую библиотеку, которую я написал, чтобы обернуть их:

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

И всегда рассматривайте их как UTC + TimeZoneInfo. Таким образом, вы можете выполнять все обычные математические операции, работая исключительно с UTC по UTC, и иметь дело только с локальными датами времени на последнем шаге, чтобы показать их пользователю в каком-то хорошем формате. Еще одним преимуществом этой структуры является то, что вы можете более точно показывать пользователю чистый часовой пояс в формате, к которому он привык, вместо того, чтобы каждый раз просматривать класс TimeZoneInfo.

...