Имеет ли .NET историю изменений часовых поясов?

Наше правительство любит менять местное время или включить | отключить летнее время.

MS развернул патч для России, чтобы принять во внимание новое изменение времени.

Теперь возникает вопрос, существует ли история изменений?

Когда я получаю UTC время дня в 01.01.2000, система должна помнить, что в московском часовом поясе было +3 UTC. (летом +4) в этот момент.

В течение 01.01.2012 у нас было +4 UTC как зимой, так и летом. И скоро у нас будет +3 UTC.

простой тест показывает, что .NET не ведет записи об изменениях:

var t = new DateTime(2012,1,1);
// UTC +4 expected
System.Console.WriteLine(t.ToLocalTime());
// UTC +4 expected
t = new DateTime(2012,06,1);
System.Console.WriteLine(t.ToLocalTime());
// UTC +3 expected
t = new DateTime(2000,1,1);
System.Console.WriteLine(t.ToLocalTime());
// UTC +4 expected
t = new DateTime(2000,6,1);
System.Console.WriteLine(t.ToLocalTime());

Существует ли какой-либо дополнительный API для решения этой проблемы?

Update:

Найден класс TimeZoneInfo и соответствующий класс AdjustmentRule. Осталось проверить, влияет ли настройка часового пояса TimeZoneInfo.Local на API DateTime.

Обновление 2: Кажется, что смещения UTC не хранятся как история, а AdjustmentRule изменяет только дневное время в течение года.

Ответ 1

.NET отслеживает некоторую историю, но это не всегда точно. Вы наткнулись на одну из неточностей.

.NET импортирует всю информацию о часовом поясе из Windows через реестр, как описано здесь и . Если вы посмотрите в реестре в HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\Russian Standard Time\Dynamic DST, вы обнаружите, что он отслеживает информацию только с 2010 года для этого часового пояса. Даты тестирования в 2000 году не будут работать хорошо, так как они вернутся к самому раннему доступному правилу (2010).

Базовая информация о смещении UTC отслеживается в реестре, но не в классе AdjustmentRule, который .NET импортирует. Если вы проверите правила настройки для этого часового пояса, вы обнаружите, что 2012 и 2013 не импортированы вообще:

var tz = TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time");
foreach (var rule in tz.GetAdjustmentRules())
{
    Console.WriteLine("{0:d} - {1:d}", rule.DateStart, rule.DateEnd);
}

ВЫВОД:

1/1/0001 - 12/31/2010
1/1/2011 - 12/31/2011
1/1/2014 - 12/31/2014

Даже если они существуют в реестре Windows, 2012 и 2013 не импортируются, потому что у них нет корректировок на летнее время.

Это создает проблему, когда базовое смещение изменяется - как и для этого часового пояса. Поскольку на данный момент это +3, а два года, когда он был +4, не были импортированы, тогда это будет похоже на +3 для тех недостающих лет.

Нет хорошего решения для этого, используя TimeZoneInfo. Даже если вы попытаетесь создать свои собственные часовые пояса, у вас возникнут проблемы с установкой такого рода изменений в доступные структуры данных.

К счастью, есть еще один вариант. Вы можете использовать стандартные часовые пояса IANA через Noda Time библиотека.

Следующий код использует Noda Time для соответствия тому, что вы написали в исходном коде:

DateTimeZone tz = DateTimeZoneProviders.Tzdb.GetSystemDefault();
Console.WriteLine(Instant.FromUtc(2012, 1, 1, 0, 0).InZone(tz).LocalDateTime);
Console.WriteLine(Instant.FromUtc(2012, 6, 1, 0, 0).InZone(tz).LocalDateTime);
Console.WriteLine(Instant.FromUtc(2000, 1, 1, 0, 0).InZone(tz).LocalDateTime);
Console.WriteLine(Instant.FromUtc(2000, 6, 1, 0, 0).InZone(tz).LocalDateTime);

Если ваш локальный часовой пояс еще не установлен для Москвы, вы можете изменить первую строку на:

DateTimeZone tz = DateTimeZoneProviders.Tzdb["Europe/Moscow"];

ВЫВОД:

1/1/2012 4:00:00 AM
6/1/2012 4:00:00 AM
1/1/2000 3:00:00 AM
6/1/2000 4:00:00 AM

Update

Проблема, описанная выше в AdjustmentRule не отслеживании изменений смещения базы, была описана в статье поддержки Microsoft KB3012229 и впоследствии исправлена ​​в .NET Framework 4.6, а также в .NET Core.

В исходных источниках видно, что AdjustmentRule теперь сохраняет поле m_baseUtcOffsetDelta. Хотя это поле не отображается через общедоступное свойство, оно влияет на вычисления и отражает сериализацию, если вы используете методы FromSerializedString и ToSerializedString (если кто-то действительно их использует).

Ответ 3

Данные хронологических часовых поясов очень сложны и заполнены множеством примеров небольших исключений, которые применяются в конкретных географических регионах, которые сегодня не имеют простого способа описания. Изменения не только изменяются, но и регионы, к которым они применяются.

Существует проект по моделированию истории этих данных со всего мира:

.NET не поддерживает то, что вы хотите из коробки. Сделать общее решение проблемы будет очень сложно, однако, если вам нужно только это в небольшом наборе регионов, то вы должны иметь возможность заполнить это самостоятельно, используя TimeZoneInfo класс, документация для которых:

Члены класса TimeZoneInfo поддерживают следующие операции:

  • Создание нового часового пояса, который еще не определен операционной системой

Ответ 4

Я не вижу каких-либо изменений для Сингапура (в реестре), в котором исторически было много изменений BaseUtcOffset.