Подтвердить что ты не робот

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 минуты к исходному дате времени делает локальное время 3AM, но также устанавливает смещение на +10 часов. Добавление 1 минуты к дате UTC и синтаксический анализ правильно устанавливает локальное время в 2 часа с смещением UTC +10.

Повторение с помощью

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

дает

2012-Мар-31 15: 59: 00.0000 Z
2012-Мар-31 16: 00: 00.0000 Z (Utc) = 2012-Мар-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-апр-01 03: 00: 00.0000 +10: 00 (локальный) = 2012-апр-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-Мар-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-Мар-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

4b9b3361

Ответ 1

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

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

Итак, у вас есть:

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

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

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

EDIT: Если целью является просто запомнить часовой пояс и мгновение, в 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);
    }
}

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

Ответ 2

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

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

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