TimeZoneInfo SupportsDaylightSavingTime无法返回我期望的结果,为什么?

时间:2019-03-05 19:56:42

标签: c# datetime timezone dst

当我开始在时区工作时,艰苦的工作很容易,但是我越是迷恋这个话题,就会发现自己迷失了方向。 在Stack Overflow中,已经有很多类似的问题,主要是试图解决特定的问题,但是没有一个(包括答案​​)可以帮助我理解所需的主题。 最重要的是,正如其他人所问的那样,时区似乎有所变化,Microsoft必须赶紧通过Windows Update分发更新来应对。

就我而言,我注意到至少2个TZ不正确:

  • 阿尔泰标准时间
  • 阿根廷标准时间

因为如上所述,TZ是从本地系统中获取的,所以我尝试搜索更新,但是我没有。因此,要么我真的错过了重要的事情,要么Microsoft不在乎这两个时区。

有关DST / TZ更新的信息

对于Daylight Saving Time,Microsoft拥有明确的政策并声明:

  

Microsoft努力将这些更改合并到Windows中,并通过Windows Update(WU)发布更新。通过WU发布的每个DST / TZ更新将具有最新的时间数据,并且还将取代以前发布的任何DST / TZ更新

最近的更新可以在专用的Microsoft Tech Community site

中找到

为了帮助自己理解,我创建了一个简单的控制台应用程序(请参见下面的代码),问题是这还不够。

示例中使用的最重要的类和方法的概述

        private static ConsoleColor DefaultColor;
    static void Main(string[] args)
    {
        DefaultColor = Console.ForegroundColor;

        foreach (var timeZoneInfo in TimeZoneInfo.GetSystemTimeZones().OrderBy(tz => tz.Id))
        {
            var firstQuart = new DateTime(2019, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            var secondQuart = new DateTime(2019, 4, 1, 0, 0, 0, DateTimeKind.Utc);
            var thirdQuart = new DateTime(2019, 7, 1, 0, 0, 0, DateTimeKind.Utc);
            var lastQuart = new DateTime(2019, 10, 1, 0, 0, 0, DateTimeKind.Utc);

            if (timeZoneInfo.Id == "Altai Standard Time" || 
                timeZoneInfo.Id == "Argentina Standard Time" ||
                timeZoneInfo.Id == "GMT Standard Time"
                )
           {
                Log($"{timeZoneInfo.DisplayName} (ID: {timeZoneInfo.Id})", ConsoleColor.Yellow);
                Log($"StandardName: {timeZoneInfo.StandardName}");
                Log($"DST: {timeZoneInfo.SupportsDaylightSavingTime}");
                Log($"Daylight Name: {timeZoneInfo.DaylightName}");
                Log();
                Log($"UTC Offset: {timeZoneInfo.BaseUtcOffset}");
                Log($"Dates for each quarter in this year");

                var convertedFirstQuart = TimeZoneInfo.ConvertTimeFromUtc(firstQuart, timeZoneInfo);
                var convertedSecondQuart = TimeZoneInfo.ConvertTimeFromUtc(secondQuart, timeZoneInfo);
                var convertedThirdQuart = TimeZoneInfo.ConvertTimeFromUtc(thirdQuart, timeZoneInfo);
                var convertedLastQuart = TimeZoneInfo.ConvertTimeFromUtc(lastQuart, timeZoneInfo);

                Log();
                Log($"First quarter: {TimeZoneInfo.ConvertTimeFromUtc(firstQuart, timeZoneInfo)}", ConsoleColor.Green);
                Log($"DST (DateTime.IsDaylightSavingTime): {convertedFirstQuart.IsDaylightSavingTime()}");
                Log($"DST (TimeInfo.IsDaylightSavingTime(DateTime): {timeZoneInfo.IsDaylightSavingTime(convertedFirstQuart)}");
                Log($"Ambiguous/Invalid: {timeZoneInfo.IsAmbiguousTime(convertedFirstQuart)}/{timeZoneInfo.IsInvalidTime(convertedFirstQuart)}");
                Log();
                Log($"Second quarter: {TimeZoneInfo.ConvertTimeFromUtc(secondQuart, timeZoneInfo)}", ConsoleColor.Green);
                Log($"DST (DateTime.IsDaylightSavingTime): {convertedSecondQuart.IsDaylightSavingTime()}");
                Log($"DST (TimeInfo.IsDaylightSavingTime(DateTime): {timeZoneInfo.IsDaylightSavingTime(convertedSecondQuart)}");
                Log($"Ambiguous/Invalid: {timeZoneInfo.IsAmbiguousTime(convertedSecondQuart)}/{timeZoneInfo.IsInvalidTime(convertedSecondQuart)}");
                Log();
                Log($"Third quarter: {TimeZoneInfo.ConvertTimeFromUtc(thirdQuart, timeZoneInfo)}", ConsoleColor.Green);
                Log($"DST (DateTime.IsDaylightSavingTime): {convertedThirdQuart.IsDaylightSavingTime()}");
                Log($"DST (TimeInfo.IsDaylightSavingTime(DateTime): {timeZoneInfo.IsDaylightSavingTime(convertedThirdQuart)}");
                Log($"Ambiguous/Invalid: {timeZoneInfo.IsAmbiguousTime(convertedThirdQuart)}/{timeZoneInfo.IsInvalidTime(convertedThirdQuart)}");
                Log();
                Log($"Last quarter: {TimeZoneInfo.ConvertTimeFromUtc(lastQuart, timeZoneInfo)}", ConsoleColor.Green);
                Log($"DST (DateTime.IsDaylightSavingTime): {convertedLastQuart.IsDaylightSavingTime()}");
                Log($"DST (TimeInfo.IsDaylightSavingTime(DateTime): {timeZoneInfo.IsDaylightSavingTime(convertedLastQuart)}");
                Log($"Ambiguous/Invalid: {timeZoneInfo.IsAmbiguousTime(convertedLastQuart)}/{timeZoneInfo.IsInvalidTime(convertedLastQuart)}");
                Log("==============================================================");
                Log();
            }
        }

        Console.ReadKey();
    }

    private static void Log(string message = "", ConsoleColor? color = null)
    {
        if(color.HasValue)
            Console.ForegroundColor = color.Value;

        Console.WriteLine(message);
        Console.ForegroundColor = DefaultColor;
    }
}

鉴于我的本地TZ是GMT,我们使用DST,输出如下:

enter image description here enter image description here

TimeZoneInfo.SupportsDaylightSavingTime()Official Documentation

  

以下示例检索所有时区的集合,这些时区   在本地系统上可用,并显示那些名称   不支持夏令时。

var zones = TimeZoneInfo.GetSystemTimeZones();
foreach(TimeZoneInfo zone in zones)
{
   if (! zone.SupportsDaylightSavingTime)
      Console.WriteLine(zone.DisplayName);
}

TimezoneInfo.IsDaylightSavingTime(DateTime)Official Documentation

  

表示指定的日期和时间是否在   当前TimeZoneInfo所在时区的夏时制   对象。

DateTime.IsDaylightSavingTime()Official Documentation

  

指示此DateTime实例是否在白天   保存当前时区的时间范围。

重要的是要理解(我起初不是),在 DateTime 实例上的方法 IsDaylightSavingTime 始终返回所要求的信息, 本地系统时区

分析输出

阿根廷标准时间为例,我们可以看到 TimeZoneInfo.SupportsDaylightSavingTime ,返回 true ,这是不正确的信息,因为到处都是我搜索了它,却发现相反的结果。

即使坚决支持DST似乎是不正确的,使用C#将UTC DateTime转换为ART TZ也会始终产生正确的结果。

让我觉得我仍然不了解整个图片的原因是 TimeInfo.IsDaylightSavingTime(DateTime)返回 false ,这就是我要期待。

永久性夏令时

根据维基百科https://en.wikipedia.org/wiki/Daylight_saving_time 有时会提倡采用“永久夏令时”(全年都保持夏令时不变)的做法,目前在阿根廷,白俄罗斯,[78]加拿大(例如萨斯喀彻温省),冰岛,吉尔吉斯斯坦,马来西亚,摩洛哥,纳米比亚,新加坡,土耳其,土库曼斯坦和乌兹别克斯坦。[164]这可能是因为遵循了邻近地区的时区,政治意愿或其他原因。

总而言之,我的未解决问题是:

  • 为什么 TimezoneInfo.SupportsDaylightSavingTime()返回 true ,但是 TimeInfo.IsDaylightSavingTime(DateTime)返回 false
  • 除了上面解释的内容外,如何确保我拥有Microsoft的最新DST / TZ更新?

1 个答案:

答案 0 :(得分:2)

简短回答

TimeZoneInfo.SupportsDaylightSavingTime会考虑系统上可用的 all 时区数据,而不仅仅是当年的数据。在Windows跟踪Argentina Standard TimeAltai Standard Time的时段内,DST生效。


更长的答案

TimeZoneInfo.SupportsDaylightSavingTime的文档(您已经在问题中链接到该文档)说明:

  

获取一个值,该值指示时区是否具有任何夏令时规则。

不太清楚的是,它是专门指TimeZoneInfo.AdjustmentRule方法返回的TimeZoneInfo.GetAdjustmentRules对象,而这些对象是 all ,而不是本年度的规则。

Microsoft政策规定Windows会跟踪2010年以后的所有更改。  但是,在制定政策之前,某些时区(例如阿根廷)已经在跟踪更改,因此在某些情况下,您会看到更早的数据。

在Windows注册表中,您可以通过以下键找到系统知道的所有时区数据:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones

每个TimeZoneInfo.Id值都对应于该子键下的一个子键,并且调整规则(如果有)将位于该子键下的Dynamic DST下。

对于Argentina Standard Time\Dynamic DST,我们发现了2006年到2010年的数据。

image

即使没有解码数据,我们也可以看到年份之间存在差异。查看timeanddate.com here可以为我们提供详细信息:

image

似乎夏令时在2007-08和2008-09夏季生效。 (阿根廷位于南半球,夏季分为两年。)

实际上,我们可以从.NET中看到这一点:

var tz = TimeZoneInfo.FindSystemTimeZoneById("Argentina Standard Time");
var dt = new DateTime(2008, 1, 1);
var dst = tz.IsDaylightSavingTime(dt); // True

因此,TimeZoneInfo.SupportsDaylightSavingTime返回True 是适当的,因为在该时区中确实存在DST中的有效日期。

阿尔泰也可以找到相同的地方。 Windows正在跟踪2010年之前的数据,以及DST existed in 2010 there

image

请注意,它在2014年和2016年也分别更改了标准时间。这是可能存在调整规则的另一个原因。不幸的是,Windows无法在不使用标记为“ DST开始”或“ DST结束”的转换的情况下对此建模。因此,即使过渡的任何一方都没有被认为是夏令时,这些年的某些时候也会从True返回IsDaylightSavingTime。这是Windows上时区的已知问题。这是一个折衷方案,即使过渡是为了更改标准时间而不是夏令时,也要确保使用正确的UTC偏移量。

如果您绝对需要了解转换是否与DST相关,则可以通过IANA time zone data库使用Noda Time来代替。 ZoneInterval.Savings属性将告诉您。 但是,这可以使您了解“永久夏令时”。即使在IANA数据库中,这些也通常被视为对标准时间的更改,而不是真正的夏时制。因此,您可能会发现有人可能会说“我们已经使用永久性DST多年了”,但数据不同意的情况。

关于最后一个问题:

  

除了上面解释的内容之外,如何确保我拥有Microsoft的最新DST / TZ更新?

只需确保您正在运行Windows Update就足够了。社区站点中列出的所有DST / TZ更新都会与常规更新一起部署到所有受支持的Windows版本,无论您处于哪个时区。