野田时区间隔

NodaTime ZoneIntervals

我正在尝试构建一组逻辑时区记录,代表一个 5 年的时间段,其中停止和开始 date/time 与人们认为的时间变化时刻夏令时相匹配。比如在America/New_York id中,我要:

03/13/2016 2:00 am    -   11/06/2016  01:59:59 am  gmtOffset:  -4:00
11/06/2016 2:00 am    -   03/12/2017  01:59:59 am  gmtOffset:  -5:00

我能够建立这些记录,但除了在夏令时生效的 1912 年之前开始并跟踪所有区域间隔的结束时间外,我别无他法。当间隔记录周期开始时,除了先前的间隔记录之外,我没有看到一种方法来确定有效的偏移量。有没有办法只使用一个 ZoneInterval 记录来做到这一点?

代码中注明的问题:

    private void processTZ()
    {
        IDateTimeZoneProvider provider = DateTimeZoneProviders.Tzdb;

        using (StreamWriter sw = new StreamWriter(@"c:\tzOut.txt"))
        {
            foreach (string id in provider.Ids)
            {
                sw.WriteLine("Id: {0}", id);
                DateTimeZone zone = provider[id];

                // Time period I wish to create logical representation.
                Instant instantValidityBegin = (new LocalDateTime(2014, 1, 1, 0, 0)).InZoneLeniently(zone).ToInstant();
                Instant instantValidityEnd = (new LocalDateTime(2019, 12, 31, 23, 59, 59, 0)).InZoneLeniently(zone).ToInstant();

                // Time period from zone intervals to iterate. The first year dst was observed was 1916.
                Instant instantFetchYearBegin = (new LocalDateTime(1916, 1, 1, 0, 0)).InZoneLeniently(zone).ToInstant();
                Instant instantFetchYearEnd = (new LocalDateTime(2019, 12, 31, 23, 59, 0)).InZoneLeniently(zone).ToInstant();

                // Get the intervals
                IEnumerable<NodaTime.TimeZones.ZoneInterval> intervals = zone.GetZoneIntervals(instantFetchYearBegin, instantFetchYearEnd);

                // Determine number of intervals for this tzId.
                int count = 0;
                foreach (NodaTime.TimeZones.ZoneInterval zi in intervals)
                    count++;

                bool singleEntry = (1 == count);

                // myStart and myEnd are desired output period
                // capture IsoLocalEnd of an interval to be used as the start date of program output (myStart).  Don't display older than 1900.
                DateTime myStart, myEnd, prevEnd = new DateTime(1900, 1, 1);

                foreach (NodaTime.TimeZones.ZoneInterval zi in intervals)
                {
                    if (singleEntry)
                    {
                        // No question here
                    }
                    else
                    {
                        // skip intervals for this tzId that represent a period beginning after time period I want.
                        if (instantValidityEnd < zi.Start)
                            break;

                        // Skip intervals ending prior to time period I want, but capture the IsoLocalEnd to be used for report output.
                        if (zi.End < instantValidityBegin)
                        {
                            prevEnd = (zi.End == Instant.MinValue) ? prevEnd = new DateTime(1900, 1, 1) : prevEnd = zi.IsoLocalEnd.ToDateTimeUnspecified(); ;
                            continue;
                        }

                        //   ***Question***  Can this myStart value be determined using the current interval record instead of storing the previous interval's IsoLocalEnd??
                        myStart = prevEnd;
                        if (zi.End == Instant.MaxValue)
                            prevEnd = myEnd = new DateTime(9999, 12, 31);
                        else
                        {
                            prevEnd = zi.IsoLocalEnd.ToDateTimeUnspecified();
                            myEnd = prevEnd.Subtract(TimeSpan.FromSeconds(1));  // force period back 1 second for logical representation.
                        }


                        sw.WriteLine("Name: " + zi.Name);
                        sw.WriteLine("Multi Entry: {0}", zi.ToString());
                        sw.WriteLine("myStart: {0:G}   myEnd: {1:G}    gmtOffset: {2:G}", myStart, myEnd, zi.WallOffset.ToTimeSpan());
                        sw.WriteLine();
                    }
                }

                sw.WriteLine("------------------------------------------------------------------");
                sw.WriteLine();
            }
        }
    }

在任何给定的 ZoneInterval 记录上,IsoLocalStartIsoLocalEnd 是相对于该特定间隔的,两者使用相同的 WallOffset 值。

如果您想获得实际生效的本地时间,您可以将 StartEnd 时刻转换为特定时区。

zi.Start.InZone(zone)
zi.End.InZone(zone)

您会发现起始值的 LocalDateTimeIsoLocalStart 匹配,但结束值不匹配。到观察到该时刻时,它现在已被下一个间隔的偏移量覆盖。不要忘记这些是 半开 区间。 [inclusive-start, exclusive-end).

让我们回到您最初请求中的示例:

03/13/2016 2:00 am    -   11/06/2016  01:59:59 am  gmtOffset:  -4:00
11/06/2016 2:00 am    -   03/12/2017  01:59:59 am  gmtOffset:  -5:00

它实际上并不是那样工作的。三月,当地时间从 1:59:59 am 移动到 3:00:00 am,跳过了一个多小时。所以第一行应该从 3:00 am 而不是 2:00 am 开始。在 11 月,当地时间从 01:59:59 am 移动到 1:00:00 am 重复一个小时。所以第二行应该从 1:00 am 而不是 2:00 am 开始。您的图表似乎试图使当地时间连续 - 但事实并非如此。

此外,您会发现按照惯例,许多人认为过渡时间是 2:00,而不是 1:59:59。将其视为接近 2:00。这就是 IsoLocalEnd 显示当前间隔偏移量的原因,您可以使用该字段来实现此目的。

此外,您可能想查看 how timeanddate.com 显示的内容。他们决定只显示结束值,这可能会使事情更清楚一些。