На днях я с сожалением обнаружил, что в семействе операционных систем Windows содержится неверная информация о московском времени. Примечательно, что ошибка до сих пор не исправлена, хотя просочилась в систему еще в 2011-ом году. Последствия ошибки будут показаны с помощью .NET, но это актуально для всех технологий, которые доверяют данным Windows о часовых поясах и смещениях времени.
Итак, на моей машине выставлен часовой пояс Москвы:
Создаем дату 09.03.2014 10:00 в UTC. Затем переводим ее в московское время. Как вы знаете, сейчас для Москвы круглый год действует смещение UTC+04:00:
var dt = new DateTime(2014, 3, 9, 10, 0, 0, DateTimeKind.Utc); //09.03.2014 10:00 UTC
Console.WriteLine("UTC: {0},\nMoscow: {1}",dt, dt.ToLocalTime() );
Получаем:
UTC: 09.03.2014 10:00:00,
Moscow: 09.03.2014 14:00:00
Что же, неплохо. Однако, окунемся чуть глубже в прошлое. До отмены перевода часов на зимнее время наша страна переводила стрелки часов два раза в год. Зимой московское время имело смещение UTC+03:00, а летом UTC+04:00.
Повторим операцию для даты 09.03.2010 10:00 в UTC.
var dt = new DateTime(2010, 3, 9, 10, 0, 0, DateTimeKind.Utc); //09.03.2010 10:00 UTC
Console.WriteLine("UTC: {0},\nMoscow: {1}",dt, dt.ToLocalTime() );
Получим:
UTC: 09.03.2010 10:00:00,
Moscow: 09.03.2010 14:00:00
Что-то не так. В марте 2010-го действовало зимнее время, для которого смещение должно быть +03:00 часа, а не +04:00. Аналогично, для летнего времени(июнь 2010-го):
var dt = new DateTime(2010, 6, 9, 10, 0, 0, DateTimeKind.Utc); //09.06.2010 10:00 UTC
Console.WriteLine("UTC: {0},\nMoscow: {1}",dt, dt.ToLocalTime() );
UTC: 09.06.2010 10:00:00,
Moscow: 09.06.2010 15:00:00
Летом смещение было +04:00, но мы получили смещение +05:00, которого в московском часовом поясе никогда не было!
В чем же дело?
Информация о часовых поясах хранится в реестре Windows и обновляется с помощью патчей. Вместо того, чтобы использовать базу данных tz database, в которой актуализируется информация о всемирных часовых поясах (и которая используется в *nix системах, BSD системах и Mac OS X), в Microsoft поддерживают собственную базу.
Если немного упростить, информация в ней хранится так. Для часового пояса указывается базовое смещение относительно UTC, затем идет информация о переходах на летнее/зимнее время. В ней говорится о том когда выполняется переход и как изменяется базовое смещение. До отмены перехода на зимнее время для Москвы действовали такие цифры:
Базовое смещение: UTC+03:00
Зимнее время = базовое смещение
Летнее время = базовое смещение +01:00
После отмены перехода на зимнее время, вышел hotfix, который должен был зафиксировать для Москвы круглогодичное смещение UTC+04:00. Но тут произошла ошибка. Вместо того, чтобы задать перманентное летнее время, в Microsoft поменяли базовое смещение с +03:00 на +04:00 и включили перманентное зимнее время. Но дело в том, что база часовых поясов Microsoft не содержит исторической информации об изменении базового смещения. В итоге для дат до лета 2010 получается следующая ситуация. Когда действует зимнее время, его смещение совпадает с базовым. До выхода патча это было +03:00, а теперь стало +04:00. Когда действует летнее время, оно имеет смещение +01:00 относительно базового. Раньше получалось +04:00, а после выхода патча стало +05:00.
Через какое-то время данная проблема была замечена, и в Microsoft подготовили очередной hotfix, который должен был исправить сложившуюся ситуацию. Однако его установка не исправляет проблему.
На текущий момент экспериментально выяснено, что ошибка воспроизводится на Windows 7, Windows 8, а так же Windows Server 2008 R2. В случае .NET данную проблему можно решить, работая с датами с помощью библиотеки NodaTime, которая использует базу данных tz database.