c# - 计算 DST 转换边界处的持续时间(向前/后退的数量)
问题描述
从这个很好的答案中,我能够确定夏令时转换日期: https ://stackoverflow.com/a/24378695/1159939
除了这些日期之外,我还需要知道时钟是否会增加或减少以及增加或减少多少(总是一个小时?)。
例如,在美国/太平洋时区,当本地时钟达到 2020-03-08T02:00:00 时,我需要以某种方式获取值 +1h。当时钟达到 2020-11-01T02:00:00 时,我需要获取值 -1h。
在 NodaTime 中,有一个 Offset.Savings 值为 +0 或 +1。现在确定我可以如何使用它。
--Update1: 我正在构建一个调度程序。我需要让用户更好地控制在 1 小时的节省期间发生计划作业时如何执行作业。
我正在考虑让用户可以选择以下设置:
[x] 当时钟向前推进时运行错过的工作。
[x] 时钟回落时重新运行作业。
例如,假设一个作业计划在 2020-03-08T02:15:00 US/Pacific 运行。这个当地时间不存在。如果用户勾选“时钟快进时运行错过的作业”复选框,该作业将在凌晨 3:15 执行,否则,该作业将被跳过。
例如,假设一个作业计划在 2020-11-01T01:45:00 US/Pacific 运行。这个当地时间将出现两次。如果用户勾选了“时钟回落时重新运行作业”,则该作业将执行两次,否则将执行一次。
为了进行上述计算,我需要知道我从前面提到的帖子中得到的夏令时转换日期。我还需要知道时钟将改变的方向以及改变多少(例如:1h)。
--更新2:
经过深思熟虑后,我想我需要一个包含以下数据的时区转换列表:
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)
下面是我用来生成这些数据的代码。不确定这是否适用于所有情况。为了计算时间变化,我使用了下一个区域间隔的开始和当前区域间隔的结束之间的差异。
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})";
}
}
}
解决方案
听起来您真正要寻找的是DateTimeZone.MapLocal(LocalDateTime)
. 这将返回一个ZoneLocalMapping
告诉您本地日期/时间如何映射到指定时区的 a。重要的属性是:
Count
- 0 如果日期/时间被跳过
- 1 如果它被明确映射
- 2 如果映射不明确
EarlyInterval
以及围绕指定的任何转换之前/之后LateInterval
的值(如果值不在转换中,则相同)ZoneInterval
LocalDateTime
ZoneInterval
ZoneInterval
包含 a WallOffset
,它是该区域间隔期间的总体 UTC 偏移量。我强烈建议使用 that 而不是Savings
, 以应对不是夏令时转换的转换(例如,如果区域的标准时间发生变化)。
您应该能够使用该信息来确定何时运行事物。
您还可以DateTimeZone.ResolveLocal(LocalDateTime, ZoneLocalMappingResolver)
根据用户选择的内容来使用构建自己的解析器(用于处理跳过/不明确的时间)的位置。
推荐阅读
- html - 单击时关闭的深色叠加层?
- android - Android使用什么样的任务调度方式?
- python - 使用 Python 3.7 在 Raspberry PI 3B 上播放视频
- r - R Shiny 在本地工作但不在服务器上
- r - 将 dan 替换为而不是 daniel
- python - google-cloud-language、pandas 和 conda 之间是否存在冲突?
- python - for循环可迭代不覆盖原始
- python - 为什么 Keras ImageDataGenerator 会抛出内存错误?
- leaflet - Leaflet markercluster 组和图层控制不起作用
- parsing - 将传感器的二进制输出解析为浮点数