TimeZoneInfo.Local vs TimeZoneInfo.FindSystemTimeZoneById

Я работал с классами DateTime и TimeZoneInfo, и я столкнулся с интересным результатом со следующим кодом:

var dstStart = new DateTime(2013, 3, 10, 2, 0, 0, DateTimeKind.Local);
var result = TimeZoneInfo.Local.IsDaylightSavingTime(dstStart);

Результатом этого является False. Я бы подумал, что это будет True (DST начинается 10 марта в 2:00)

Затем я попробовал аналогичный код, используя FindSystemTimeZoneById вместо:

var myTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
var result = myTimeZone.IsDaylightSavingTime(dstStart);

Результат этого на удивление True.

Затем я проверил, чтобы эти объекты представляли один и тот же часовой пояс:

myTimeZone.Id == TimeZoneInfo.Local.Id // returns True (Both are "Eastern Standard Time")

Мой вопрос: почему эти результаты разные, и что более важно, как я могу сделать их одинаковыми?

Мой компьютер определенно находится в Eastern Standard Time часовом поясе

Дополнительная информация:

Я перепробовал свои компьютерные часы, и я провел несколько тестов для сравнения объекта TimeZoneInfo, который был возвращен каждым из вышеперечисленных методов. Вот моя тестовая программа

var timeZoneFromLookup = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");

var dstStart = new DateTime(2013, 3, 10, 2, 0, 0, DateTimeKind.Local);

// -- The following return true --

Console.WriteLine("Equal? {0}", TimeZoneInfo.Local.Equals(timeZoneFromLookup));

Console.WriteLine("Has Same Rules? {0}", TimeZoneInfo.Local.HasSameRules(timeZoneFromLookup));

Console.WriteLine("Same Id? {0}", TimeZoneInfo.Local.Id == timeZoneFromLookup.Id);

Console.WriteLine("Same Base UTC Offset? {0}", TimeZoneInfo.Local.BaseUtcOffset == timeZoneFromLookup.BaseUtcOffset);

Console.WriteLine("Same Daylight Name? {0}", TimeZoneInfo.Local.DaylightName == timeZoneFromLookup.DaylightName);

Console.WriteLine("Same Display Name? {0}", TimeZoneInfo.Local.DisplayName == timeZoneFromLookup.DisplayName);

Console.WriteLine("Same Standard Name? {0}", TimeZoneInfo.Local.StandardName == timeZoneFromLookup.StandardName);

Console.WriteLine("Same Support For DST? {0}", 
    TimeZoneInfo.Local.SupportsDaylightSavingTime == timeZoneFromLookup.SupportsDaylightSavingTime
);

Console.WriteLine("Same result as to whether date/time is ambiguous? {0}", 
    timeZoneFromLookup.IsAmbiguousTime(dstStart) == TimeZoneInfo.Local.IsAmbiguousTime(dstStart)
);

// -- The following return false --


Console.WriteLine("Same utc offset result? {0}", 
    timeZoneFromLookup.GetUtcOffset(dstStart) == TimeZoneInfo.Local.GetUtcOffset(dstStart)

);
Console.WriteLine("Same Conversion to UTC? {0}", 
    TimeZoneInfo.Local.GetUtcOffset(dstStart) == timeZoneFromLookup.GetUtcOffset(dstStart)
);

Console.WriteLine("Same result as to whether date/time is invalid? {0}", 
    timeZoneFromLookup.IsInvalidTime(dstStart) == TimeZoneInfo.Local.IsInvalidTime(dstStart)
);

Console.WriteLine("Same result as to whether date/time is DST? {0}", 
    timeZoneFromLookup.IsDaylightSavingTime(dstStart) == TimeZoneInfo.Local.IsDaylightSavingTime(dstStart)
);

Ответ 1

Я немного размышлял, и я считаю, что несогласованность проистекает из того, как System.TimeZoneInfo+CachedData.GetCorrespondingKind(TimeZoneInfo timeZone) возвращает DateTimeKind.Local только в том случае, когда timeZone == this.m_localTimeZone (т.е. когда аргумент был тем же самым экземпляром, что и свойство TimeZoneInfo.Local основанный на).

В случае, когда вы передаете этот другой экземпляр TimeZoneInfo, который вы получили от TimeZoneInfo.FindSystemTimeZoneById, я ожидаю, что он вернет DateTimeKind.Unspecified.

Это (возможно, между прочим) влияет на System.TimeZoneInfo.IsDaylightSavingTime(DateTime dateTime), где в случае, когда dateTime.Kind является локальным, он выполняет преобразование между существенно TimeZoneInfo.Local и вашим экземпляром TimeZoneInfo и основывает преобразование на то, что GetCorrespondingKind говорит о исходных и целевых часовых поясах (преобразование возвращает исходное datetime в случае, когда источник и цель являются локальными).

Ответ 2

Разница в поведении при использовании нелокального TimeZoneInfo определяется в документации MSDN.

Причина, по которой первый результат False заключается в том, что созданный вами объект DateTime технически неоднозначен. Нет 2:00 утра 10 марта 2013 года в местном часовом поясе EST.

В документе указывается, что метод IsDaylightSavingTime() "зависит от отношения между часовым поясом, представленным объектом TimeZoneInfo, и свойством Kind для параметра dateTime". В таблице в разделе "Примечания" дается описание каждой из возможных комбинаций.

При указании часового пояса путем явного вызова FindSystemTimeZoneById аргумент DateTime "Local Kind" сначала преобразуется из Local в указанный часовой пояс. На этом шаге неопределенное время разрешено в законное значение.

Попробуйте добавить это к вашему тесту:

var dstStart = new DateTime(2013, 3, 10, 2, 0, 0, DateTimeKind.Local);

dstStart = dstStart.ToUniversalTime();
dstStart = TimeZoneInfo.ConvertTime(dstStart, TimeZoneInfo.Utc, myTimeZone);

Конечное значение dstStart становится '3/10/2013 3:00:00 AM' (если ваша машина по-прежнему находится в EST). Такое же преобразование происходит в IsDaylightSavingTime() по параметру локального вида, что иллюстрирует, почему он возвращает True.

Настоящий сюрприз заключается в том, что метод IsDaylightSavingTime() не поднимает ArgumentException в любом случае. В документации говорится, что он выдаст исключение, если будет указан недопустимый параметр DateTime, тип которого DateTimeKind.Local, но это явно не так.

Edit:

После просмотра исходного кода и комментариев для класса TimeZoneInfo я пришел к выводу, что метод IsDaylightSavingTime() не предназначен для исключения исключений. Это ошибка в документации.