Calculate duration (amount to spring forward/fall back) at DST transitions boundaries

半城伤御伤魂 提交于 2021-01-05 07:19:26

问题


From this great answer, I am able to determine daylight saving transitions dates: https://stackoverflow.com/a/24378695/1159939

In addition of those dates, I need to know if the clock is going to increase or decrease and by what amount (is it always an hour?).

For example, in time zone US/Pacific, when the local clock reaches 2020-03-08T02:00:00, I need to somehow get value +1h. When the clock reaches 2020-11-01T02:00:00, I need to get value -1h.

In NodaTime, there is a Offset.Savings value that is +0 or +1. Now sure how I can use that.

--Update1: I am building a scheduler. I need to give the user more control over how jobs are executed when a scheduled job is to occur during the 1-hour savings period.

I am thinking for having the following settings the user can select:

[x] Run missed jobs when clocks spring forward.

[x] Re-run jobs when clocks fall back.

For example, let's say a job is scheduled to run at 2020-03-08T02:15:00 US/Pacific. This local time does not exist. If the user ticks off the "Run missed jobs when clocks spring forward" checkbox, the job will be executed at 3:15AM, otherwise, the job will be skipped.

For example, let's say a job is scheduled to run at 2020-11-01T01:45:00 US/Pacific. This local time will occur twice. If the user ticks off the "Re-run jobs when clocks fall back", the job will be executed twice, otherwise, it will be executed once.

In order to make the calculations above, I need to know the day light saving transition dates which I got from the post mentioned previously. I also need to know which direction the clocks will change and by how much (e.g.: 1h).

--Update2:

After giving it more thought, I think I need a list of time zone transitions that contains the following data:

2020-03-08 02:00:00 US/Eastern | 2020-03-08 07:00:00 UAT (01:00:00)
2020-11-01 02:00:00 US/Eastern | 2020-11-01 06:00:00 UAT (-01:00:00)

Below is the code that I am using to generate this data. Not sure if this will work in all cases. In order to calculate time change, I am using the difference between the start of the next zone interval and the end of the current zone interval.

using NodaTime;
using System;
using System.Collections.Generic;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            string timeZoneId = "US/Eastern";
            DateTimeZone? timeZone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(timeZoneId);

            if (timeZone is null)
                throw new Exception($"Cannot find time zone '{timeZoneId}'.");

            int year = 2020;

            var daylightSavingTransitions = GetDaylightSavingTransitions(timeZone, year);

            foreach (var daylightSavingTransition in daylightSavingTransitions)
            {
                Console.WriteLine(daylightSavingTransition);
            }


            /// <summary>
            /// Get points in time when a daylight saving time transitions occur.
            /// </summary>
            /// <param name="timeZone">Time zone of the local clock.</param>
            /// <param name="year">The year to find transitions.</param>
            /// <returns></returns>
            static IEnumerable<DaylightSavingTransition> GetDaylightSavingTransitions(DateTimeZone timeZone, int year)
            {
                var yearStart = new LocalDateTime(year, 1, 1, 0, 0).InZoneLeniently(timeZone).ToInstant();
                var yearEnd = new LocalDateTime(year + 1, 1, 1, 0, 0).InZoneLeniently(timeZone).ToInstant();

                LinkedList<NodaTime.TimeZones.ZoneInterval> zoneIntervals = new LinkedList<NodaTime.TimeZones.ZoneInterval>(timeZone.GetZoneIntervals(yearStart, yearEnd));
                LinkedListNode<NodaTime.TimeZones.ZoneInterval>? currentNode = zoneIntervals.First;

                while (currentNode is { })
                {
                    if (currentNode.Next is null)
                        break;

                    //Time change is the difference between the start of the next zone interval and the end of the current zone interval.
                    Period timeChangePeriod = currentNode.Next.Value.IsoLocalStart - currentNode.Value.IsoLocalEnd;
                    TimeSpan timeChange = new TimeSpan(Convert.ToInt32(timeChangePeriod.Hours), Convert.ToInt32(timeChangePeriod.Minutes), Convert.ToInt32(timeChangePeriod.Seconds));
                    DaylightSavingTransition daylightSavingTransition = new DaylightSavingTransition(timeZone.Id, currentNode.Value.IsoLocalEnd.ToDateTimeUnspecified(), currentNode.Value.End.ToDateTimeUtc(), timeChange);
                    yield return daylightSavingTransition;

                    currentNode = currentNode.Next;
                }
            }
        }
    }



    public class DaylightSavingTransition
    {
        public DaylightSavingTransition(string timeZoneId, DateTime transitionLocalDate, DateTime transitionUtcDate, TimeSpan timeChange)
        {
            TimeZoneId = timeZoneId;
            TransitionLocalDate = DateTime.SpecifyKind(transitionLocalDate, DateTimeKind.Unspecified);
            TransitionUtcDate = DateTime.SpecifyKind(transitionUtcDate, DateTimeKind.Utc);
            TimeChange = timeChange;
        }

        public string TimeZoneId { get; }
        public DateTime TransitionLocalDate { get; }
        public DateTime TransitionUtcDate { get; }
        public TimeSpan TimeChange { get; }

        /// <summary>
        /// For fall back transition, used to determine if date is in the duplicated time period.
        /// </summary>
        /// <param name="utcDateTime">Point in time to test if it is inside the repeating time period.</param>
        public bool IsRepeatingDateTime(DateTime utcDateTime)
        {
            if (utcDateTime >= TransitionUtcDate && utcDateTime < TransitionUtcDate.Add(TimeChange.Duration()))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public override string ToString()
        {
            return $"{TransitionLocalDate.ToString("yyyy-MM-dd HH:mm:ss")} {TimeZoneId} | {TransitionUtcDate.ToString("yyyy-MM-dd HH:mm:ss")} UAT ({TimeChange})";
        }
    }
}

回答1:


It sounds like what you're really looking for is DateTimeZone.MapLocal(LocalDateTime). That returns a ZoneLocalMapping which tells you how a local date/time is mapped into the specified time zone. The important properties are:

  • Count
    • 0 if the date/time is skipped
    • 1 if it's mapped unambiguously
    • 2 if it's mapped ambiguously
  • EarlyInterval and LateInterval which are the ZoneInterval values for before/after any transition around the specified LocalDateTime (or the same ZoneInterval if the value isn't in a transition)

ZoneInterval contains a WallOffset which is the overall UTC offset during that zone interval. I'd strongly recommend using that rather than Savings, in order to cope with transitions which aren't daylight saving transitions (e.g. if the standard time for a zone changes).

You should be able to use that information to determine when to run things.

You could also use DateTimeZone.ResolveLocal(LocalDateTime, ZoneLocalMappingResolver) where you'd build your own resolver (for handling skipped/ambiguous times) based on what the user has selected.



来源:https://stackoverflow.com/questions/65096574/calculate-duration-amount-to-spring-forward-fall-back-at-dst-transitions-bound

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!