问题
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
andLateInterval
which are theZoneInterval
values for before/after any transition around the specifiedLocalDateTime
(or the sameZoneInterval
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