c# - 在计算日期时间之间的持续时间时安全处理夏令时(或任何其他理论上的非常量偏移)

标签 c# date timezone dst

我知道,即使在过去 24 小时内,这也不是第一次提出这个话题,但令我惊讶的是,我还没有找到针对此问题的明确/最佳实践解决方案。这个问题似乎也与我认为的将所有日期保存为 UTC 的简单设计决定相矛盾。我将尝试在这里陈述问题:

给定两个 DateTime 对象,计算它们之间的持续时间,同时考虑夏令时。

考虑以下场景:

  1. UtcDate - LocalDate,其中 LocalDate 比 a 早 1 毫秒 夏令时切换。

  2. LocalDateA - LocalDateB,其中 LocalDateB 为 1 比 DST 切换早毫秒。

UtcDate - LocalDate.ToUtc() 提供不考虑 DST 切换的持续时间。 LocalDateA.ToUtc() - LocalDateB.ToUtc() 是正确的,但 LocalDateA - LocalDateB 也忽略 DST。

现在,显然这个问题有解决方案。我现在使用的解决方案是这个扩展方法:

public static TimeSpan Subtract(this DateTime minuend, TimeZoneInfo minuendTimeZone, 
    DateTime subtrahend, TimeZoneInfo subtrahendTimeZone)
{
    return TimeZoneInfo.ConvertTimeToUtc(DateTime.SpecifyKind(minuend, 
        DateTimeKind.Unspecified), minuendTimeZone)
        .Subtract(TimeZoneInfo.ConvertTimeToUtc(DateTime.SpecifyKind(subtrahend, 
            DateTimeKind.Unspecified), subtrahendTimeZone));
}

我想它有效。不过我有一些问题:

  1. 如果日期在保存之前全部转换为 UTC,则此 方法没有帮助。时区信息(以及任何处理 DST)丢失。我习惯于始终以 UTC 格式保存日期,是 夏令时问题的影响力不足以使其成为坏事 决定?

  2. 不太可能有人知道这种方法,甚至 考虑这个问题,在计算之间的差异时 日期。有更安全的解决方案吗?

  3. 如果我们共同努力,也许科技行业可以说服 国会废除夏令时。

最佳答案

正如您所指出的,这个问题之前已经讨论过。 Herehere有两篇值得回顾的好帖子。

此外,the documentation DateTime.Subtract 有这样的说法:

The Subtract(DateTime) method does not consider the value of the Kind property of the two DateTime values when performing the subtraction. Before subtracting DateTime objects, ensure that the objects represent times in the same time zone. Otherwise, the result will include the difference between time zones.

Note

The DateTimeOffset.Subtract(DateTimeOffset) method does consider the difference between time zones when performing the subtraction.

除了“表示同一时区的时间”之外,请记住,即使对象位于同一时区,DateTime 值的减法仍然不会考虑 DST 或其他转换位于两个对象之间。

关键点是,要确定耗时,您应该减去时间的绝对点。这些最好由 .NET 中的 DateTimeOffset 表示。

如果您已有 DateTimeOffset 值,则只需减去它们即可。不过,您仍然可以使用 DateTime 值,只要您首先将它们正确地转换为 DateTimeOffset 即可。

或者,您可以将所有内容转换为 UTC - 但您必须通过 DateTimeOffset 或类似代码才能正确执行此操作。

根据您的情况,您可以将代码更改为以下内容:

public static TimeSpan Subtract(this DateTime minuend, TimeZoneInfo minuendTimeZone, 
    DateTime subtrahend, TimeZoneInfo subtrahendTimeZone)
{
    return minuend.ToDateTimeOffset(minuendTimeZone) -
        subtrahend.ToDateTimeOffset(subtrahendTimeZone);
}

您还需要 ToDateTimeOffset 扩展方法 ( which I've also used on other answers )。

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));
}

关于c# - 在计算日期时间之间的持续时间时安全处理夏令时(或任何其他理论上的非常量偏移),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59847174/

相关文章:

c# - 如何在 Guid/UNIQUEIDENTIFIER (SQL Server) 中解释 #

python - 用于提取不同格式的日期 (d,m,y) 的通用 Python 正则表达式

java - 将 Android 中的 Joda 时间转换为本地时区

c# - 日期时间本地化。任何标准/最佳实践

c# - String.Format() 带前导零的十六进制不适用于第二个参数

c# - 基于平台或字节序的条件编译器指令?

c# - 在运行时将 List<object> 转换为 List<T>

ios - 如何在特定时间和日期添加日历事件?

Java 日期解析 - 即使在 "wrong"情况下也能工作

mysql - 在 django 模型中存储时区的假设?