首页 文章

.NET是否具有时区更改的历史记录?

提问于
浏览
15

我们的政府喜欢改变当地时间或启用夏令时 .

MS为俄罗斯部署补丁以考虑新的时间变化 .

现在有一个问题,即变化的历史是否存在?

当我在01.01.2000系统中获得当天的UTC时间时,系统应该记住莫斯科时区有3 UTC . (在夏天4)那一刻 .

对于2012年1月1日,我们在冬季和夏季都有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 时区的自定义是否会影响DateTime API .

Update 2: 似乎UTC偏移不会存储为历史记录, AdjustmentRule 仅会更改一年中的白天时间 .

3 回答

  • 2

    .NET跟踪一些历史记录,但并不总是准确的 . 你偶然发现了一个不准确之处 .

    .NET通过注册表从Windows导入所有时区信息,如herehere所述 . 如果您在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\Russian Standard Time\Dynamic DST 查看注册表,您会发现它只跟踪此时区2010年前进的信息 . 2000年的测试日期不会很好,因为它将回溯到最早的规则(2010年) .

    基础UTC偏移信息在注册表中进行跟踪,但不在.NET将其导入的 AdjustmentRule 类中进行跟踪 . 如果您检查此时区的调整规则,您会发现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);
    }
    

    OUTPUT:

    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 没有很好的解决方案 . 即使您尝试创建自己的自定义时区,也无法在可用的数据结构中进行此类更改 .

    幸运的是,还有另一种选择 . 您可以通过Noda Time库使用标准IANA time zones .

    以下代码使用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"];
    

    OUTPUT:

    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支持文章KB3012229中进行了描述,随后在.NET Framework 4.6和.NET Core中进行了修复 .

    the reference sources中,可以看到 AdjustmentRule 现在保持 m_baseUtcOffsetDelta 字段 . 虽然此字段不是通过公共属性公开的,但它确实考虑了计算,如果使用 FromSerializedStringToSerializedString 方法(如果有人实际使用这些方法),它确实反映在序列化中 .

  • 2
  • 7

    历史时区数据非常复杂,并且充满了许多适用于特定地理区域的小例外情况的例子,这些例外今天没有简单的描述方式 . 不仅偏移在变化,而且它们应用的区域也在变化 .

    有一个项目来模拟来自世界各地的这些数据的历史:

    .NET不支持开箱即用的功能 . 对问题进行一般解决方案将非常困难,但是如果您只需要在一小部分区域内进行解决,那么您应该能够使用TimeZoneInfo class自己填充这个,这些文档的状态如下:

    TimeZoneInfo类的成员支持以下操作:创建尚未由操作系统定义的新时区

相关问题