Ответ 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
(если кто-то действительно их использует).