Есть ли в.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 для решения проблемы?
Обновить:
Нашли класс TimeZoneInfo и связанный с ним класс AdjustmentRule. Осталось проверить, есть ли настройки TimeZoneInfo.Local
часовой пояс влияет на API DateTime.
Обновление 2: похоже, что смещения UTC не сохраняются как история и AdjustmentRule
меняется только дневное время в течение года.
3 ответа
.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.
Следующий код использует 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
Обновить
Описанная выше проблема AdjustmentRule
не отслеживание изменений базового смещения было описано в статье Microsoft Support KB3012229 и впоследствии исправлено в.NET Framework 4.6, а также в.NET Core.
Из справочных источников видно, что AdjustmentRule
сейчас держит m_baseUtcOffsetDelta
поле. Хотя это поле не доступно через общедоступное свойство, оно учитывает расчеты и отражается в сериализации, если вы используете FromSerializedString
а также ToSerializedString
методы (если кто-то действительно использует их).
Вы можете попробовать NodaTime,
лучшее объяснение того, что делает
http://blog.nodatime.org/2011/08/what-wrong-with-datetime-anyway.html
Исторические данные о часовых поясах очень сложны и содержат множество примеров небольших исключений, которые применяются в конкретных географических регионах, которые сегодня не поддаются простому описанию. Меняются не только смещения, но и регионы, к которым они применяются.
Здесь есть проект для моделирования истории этих данных со всего мира:
.NET не поддерживает то, что вы хотите из коробки. Общее решение проблемы будет очень трудным, однако, если оно понадобится вам только в небольшом наборе регионов, вы сможете заполнить его самостоятельно, используя TimeZoneInfo
класс, в документации к которому говорится:
Члены класса TimeZoneInfo поддерживают следующие операции:
- Создание нового часового пояса, который еще не определен операционной системой