c# - C# - 处理 DST 过渡日的主要时间范围 - 提供的 DateTime 表示无效时间
问题描述
几个前提:
- 我所说的“流行时间”是指它是如何在本地处理的(我的行业使用这个术语)。例如,东部流行时间的 UTC 偏移量为 -05:00,但 DST 期间为 -04:00
- 我发现通过将最终值视为排他性而不是骇人听闻的包容性方法(您必须从超出范围末尾的第一个值中减去一个epsilon )来处理范围数据要干净得多。
例如,根据区间表示法,从 0(包括)到 1(不包括)的值范围[0, 1)
是epsilon值取决于所使用的数据类型)。[0, 0.99999999999...]
考虑到这两个想法,当结束时间戳无效(即没有凌晨 2 点,立即变为凌晨 3 点)时,如何表示春季 DST 过渡日的最后一小时时间范围?
[2019-03-10 01:00, 2019-03-10 02:00)
在您选择的支持 DST 的时区。
将结束时间设置为03:00
非常具有误导性,因为它看起来像是一个 2 小时宽的时间范围。
当我通过这个 C# 示例代码运行它时,它爆炸了:
DateTime hourEnd_tz = new DateTime(2019, 3, 10, 0, 0, 0, DateTimeKind.Unspecified);//midnight on the spring DST transition day
hourEnd_tz = hourEnd_tz.AddHours(2);//other code variably computes this offset from business logic
TimeZoneInfo EPT = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");//includes DST rules
DateTime hourEnd_utc = TimeZoneInfo.ConvertTime(//interpret the value from the user's time zone
hourEnd_tz,
EPT,
TimeZoneInfo.Utc);
System.ArgumentException : '提供的 DateTime 表示无效时间。例如,当时钟向前调整时,被跳过的周期内的任何时间都是无效的。参数名称:日期时间'
我该如何处理这种情况(在其他地方我已经在处理秋天的模糊时间),而不必广泛重构我的时间范围类库?
解决方案
前提 1 是合理的,尽管“普遍”这个词经常被删除,它只是被称为“东部时间”——两者都可以。
前提 2 是最佳实践。半开范围提供了许多好处,例如不必处理涉及 epsilon 的日期数学,或者不必确定 epsilon 应该具有的精度。
但是,您试图描述的范围不能仅通过日期和时间来完成。它还需要涉及与 UTC 的偏移量。对于美国东部时间(使用 ISO 8601 格式),它看起来像这样:
[2019-03-10T01:00:00-05:00, 2019-03-10T03:00:00-04:00) (spring-forward)
[2019-11-03T02:00:00-04:00, 2019-11-03T02:00:00-05:00) (fall-back)
你说:
将结束时间设置为 03:00 非常具有误导性,因为它看起来像是一个 2 小时宽的时间范围。
啊,但是将春季结束时间设置为 02:00 也会产生误导,因为当天没有观察到当地时间。只有将实际的本地日期和时间与当时的偏移量结合起来才能准确。
您可以使用DateTimeOffset
.NET 中的结构对这些(或Noda TimeOffsetDateTime
中的结构)进行建模。
我该如何处理这种情况......而不必广泛重构我的时间范围类库?
首先,您需要一个扩展方法,可以让您从特定时区转换为 a DateTime
。DateTimeOffset
你需要这个有两个原因:
构造
new DateTimeOffset(DateTime)
函数假定 aDateTime
withKind
ofDateTimeKind.Unspecified
应被视为本地时间。没有机会指定时区。该
new DateTimeOffset(dt, TimeZoneInfo.GetUtcOffset(dt))
方法还不够好,因为GetUtcOffset
假定您在模棱两可或无效的情况下需要标准时间偏移。通常情况并非如此,因此您必须自己编写以下代码:
public static DateTimeOffset ToDateTimeOffset(this DateTime dt, TimeZoneInfo tz)
{
if (dt.Kind != DateTimeKind.Unspecified)
{
// Handle UTC or Local kinds (regular and hidden 4th kind)
DateTimeOffset dto = new DateTimeOffset(dt.ToUniversalTime(), TimeSpan.Zero);
return TimeZoneInfo.ConvertTime(dto, tz);
}
if (tz.IsAmbiguousTime(dt))
{
// Prefer the daylight offset, because it comes first sequentially (1:30 ET becomes 1:30 EDT)
TimeSpan[] offsets = tz.GetAmbiguousTimeOffsets(dt);
TimeSpan offset = offsets[0] > offsets[1] ? offsets[0] : offsets[1];
return new DateTimeOffset(dt, offset);
}
if (tz.IsInvalidTime(dt))
{
// Advance by the gap, and return with the daylight offset (2:30 ET becomes 3:30 EDT)
TimeSpan[] offsets = { tz.GetUtcOffset(dt.AddDays(-1)), tz.GetUtcOffset(dt.AddDays(1)) };
TimeSpan gap = offsets[1] - offsets[0];
return new DateTimeOffset(dt.Add(gap), offsets[1]);
}
// Simple case
return new DateTimeOffset(dt, tz.GetUtcOffset(dt));
}
现在您已经定义了它(并将其放在项目中的某个静态类中),您可以在应用程序中需要的地方调用它。
例如:
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTime dt = new DateTime(2019, 3, 10, 2, 0, 0, DateTimeKind.Unspecified);
DateTimeOffset dto = dt.ToDateTimeOffset(tz); // 2019-03-10T03:00:00-04:00
或者
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTime dt = new DateTime(2019, 11, 3, 1, 0, 0, DateTimeKind.Unspecified);
DateTimeOffset dto = dt.ToDateTimeOffset(tz); // 2019-11-03T01:00:00-04:00
或者
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTime dt = new DateTime(2019, 3, 10, 0, 0, 0, DateTimeKind.Unspecified);
DateTimeOffset midnight = dt.ToDateTimeOffset(tz); // 2019-03-10T00:00:00-05:00
DateTimeOffset oneOClock = midnight.AddHours(1); // 2019-03-10T01:00:00-05:00
DateTimeOffset twoOClock = oneOClock.AddHours(1); // 2019-03-10T02:00:00-05:00
DateTimeOffset threeOClock = TimeZoneInfo.ConvertTime(twoOClock, tz); // 2019-03-10T03:00:00-04:00
TimeSpan diff = threeOClock - oneOClock; // 1 hour
请注意,DateTimeOffset
正确减去两个值会考虑它们的偏移量(而减去两个DateTime
值会完全忽略它们的Kind
)。
推荐阅读
- android - 意图过滤器操作未打开正确的活动
- html - 使用列表/数据列表输入:删除 Chrome 版本 91 中的下拉箭头
- wordpress - 如何获取 Woocommerce 产品图片库的 HTML(带拇指)并将其添加到钩子之外
- reactjs - 反应添加到图表
- java - spring cloud gateway中如何在运行时调用不同的主机
- flutter - 在动画中为 2 个文本小部件依次创建幻灯片的最佳方法是一个接一个。扑?
- javascript - 开玩笑地模拟特定的配置值
- javascript - 反应如何等待组件/页面呈现调用函数
- r - Reactable Shiny - 更新单个单元格值
- r - 使用“count”时,没有适用于“filter_”的方法应用于“c('double','numeric')”类的对象