DateTime - странное поведение при переходе на летнее время.

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

Сб 31 марта 2012 15:59 UTC = вс, 01 апреля 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-апр-01 03: 00: 00.0000 +10: 00 (местный) = 2012-апр-01 02: 00: 00.0000 +10: 00 ( Местный)
15:17:16

Добавление 1 минуты к исходному datetime делает местное время 3AM, но также устанавливает смещение на +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);

or

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

дает

31 марта 2012 г. 15: 59: 00.0000 Z
31 марта 2012 г. 16: 00: 00.0000 Z (Utc) = 31 марта 2012 г. 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-апр-01 03: 00: 00.0000 +10: 00 (местный) = 2012-апр-01 02: 00: 00.0000 +10: 00 ( Местный)
15:17:16

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

Неприлично, если я использую TimeZoneInfo для преобразования из UTC в стандартное восточное время Австралии, я получаю правильное время, но теряю информацию о смещении в экземпляре 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: 31 марта 2012 г. 15: 59: 00.0000 Z
Оригинал в локальном формате: 01 апреля 2012 г. 02: 59: 00.0000 +11: 00
+ 1 минута в местном масштабе: 01 апреля 2012 г. 02: 00: 00.0000 +10: 00
+ 1 минута по всемирному координированному времени: 31 марта 2012 г. 16: 00: 00.0000 Z

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

Оригинал в формате UTC: 31 марта 2012 г. 15: 59: 00.0000 Z
Оригинал в локальном формате: 01 апреля 2012 г. 02: 59: 00.0000 +11: 00
+ 1 минута в местном масштабе: 01 апреля 2012 г. 03: 00: 00.0000 +10: 00
+ 1 минута по всемирному координированному времени: 31 марта 2012 г. 17: 00: 00.0000 Z


person Robert Slaney    schedule 01.03.2012    source источник
comment
DateTime НЕ сохраняет локальное смещение. Я просто показываю смещение, которое будет действовать в то время. Поскольку dt2 всегда находится в местном времени, его представление о текущем часе ИСТИННО. Вы должны использовать DateTimeOffset, если хотите сохранить примененное смещение msdn .microsoft.com / en-us / library / system.datetimeoffset.aspx.   -  person IDisposable    schedule 02.03.2012
comment
... если бы это было правдой, то я ожидал бы, что третий результат от dt2 в последнем сценарии будет 03:00:00 +11: 00, но он знает, что летнее время закончилось. Правильно перешёл на +10: 00, но час не сбил. DateTimeOffset показывает время как 03:00:00 +11: 00, что недействительно для моего местного часового пояса.   -  person Robert Slaney    schedule 02.03.2012
comment
Нет, он знает, что ВЫ СКАЗАЛИ, что это было 3:00 по состоянию на 01.04.2012, поэтому смещение В ЭТОТ момент составляет +10: 00.   -  person IDisposable    schedule 02.03.2012
comment
Я никогда не говорил, что сейчас 03:00 +10, я добавил 1 минуту к 2:59 +11. Это должно было привести к 02:00 +10. Свойство DateTime.Kind этой даты было Local   -  person Robert Slaney    schedule 02.03.2012
comment
@RobertSlaney: Проблема в том, что он выполнял локальную арифметику. Когда у вас есть DateTimeKind of Local, он не учитывает летнее время; вы не добавляете прошедшее время, вы просто добавляете местное время.   -  person Jon Skeet    schedule 02.03.2012
comment
спасибо, Джон, я прочитал, что вы можете выполнять арифметику с датой и временем ТОЛЬКО, если DateTimeKind равен UTC. Затем вам нужно перевести на местное время. Верный ? Это также означает, что строка формата K независимо оценивается от фактического, поскольку она перешла на +10, который, следовательно, учитывает DST, но время остается в 03:00:00, которое не учитывает DST.   -  person Robert Slaney    schedule 05.03.2012


Ответы (2)


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

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

Итак, эффективно у вас есть:

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

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

Мне не ясно, чего вы на самом деле пытаетесь достичь здесь - если вы можете быть более конкретным, я могу показать вам, что вы бы делали в 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);
    }
}

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

person Jon Skeet    schedule 01.03.2012
comment
Я видел сообщение в вашем блоге и был в процессе написания нескольких модульных тестов для перехода на летнее время, чтобы проверить обработку Noda Time DST, но застрял, когда увидел эти результаты только из структуры DateTime - person Robert Slaney; 02.03.2012
comment
Отметив этот ответ как правильный, поскольку в различных обсуждениях подчеркивалось, что арифметика DateTime для datetime, отличной от UTC, в .NET принципиально нарушена. Единственный способ бороться с этим - обернуть или заменить DateTime - person Robert Slaney; 05.03.2012

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

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

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

person Chris Moschini    schedule 11.04.2013