【问题标题】:Difference Between DateTime (UTC) Based on a Local Timezone基于本地时区的日期时间 (UTC) 之间的差异
【发布时间】:2015-09-28 10:31:58
【问题描述】:

我有两个 DateTime 对象,其中包含两个 UTC 日期/时间和一个用户 TimezoneId (tzdb) 作为 string。我正在尝试编写一个采用这三个参数并返回相对于时区的两个日期时间之间的总秒数(或Duration)的方法。

public static double GetDurationForTimezone(DateTime startUtc, DateTime endUtc, string timezoneId)
{
    var timezone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(timezoneId);

    // convert UTC to timezone
    var startInstantUtc = Instant.FromDateTimeUtc(startUtc);
    var startZonedDateTime = startInstantUtc.InZone(timezone);

    var endInstantUtc = Instant.FromDateTimeUtc(endUtc);
    var endZonedDateTime = endInstantUtc.InZone(timezone);

    return endZonedDateTime.ToInstant().Minus(startZonedDateTime.ToInstant()).ToTimeSpan().TotalSeconds;
}

我想按照 w.r.t.时区,以确保它考虑到在此期间可能发生的任何夏令时变化。

示例测试:

// DST starts (25h day -- DST starts: 10/4 @ 2am local time)
var result = GetDurationForTimezone(
    new DateTime(2015, 10, 3, 15, 0, 0, DateTimeKind.Utc),
    new DateTime(2015, 10, 4, 15, 0, 0, DateTimeKind.Utc),
    "Australia/Sydney");
Assert.Equal(TimeSpan.FromHours(25).TotalSeconds, result);

但在运行此测试时,似乎对 .ToInstant() 的调用并未遵循 ZonedDateTime 版本,而是原始 UTC DateTime 对象。因此,我看到结果是 24 小时。

【问题讨论】:

  • 如果您的两个输入始终采用 UTC,并且您想要它们之间的经过时间,则不应涉及时区。 UTC 全球统一,没有夏令时。
  • 测试也有缺陷。无论您在本地的哪个时区,这两个基于 UTC 的值之间都正好有 24 小时。

标签: c# nodatime


【解决方案1】:

在确定基于 UTC 的时间戳之间的持续时间时,时区无关紧要

UTC 是Coordinated Universal Time。这对地球上的每个人来说都是一样的。它没有夏令时,并且它的偏移量始终为零(UTC+00:00)。

由于您已经断言输入值采用 UTC,因此您不必为此操作使用 Noda Time。只需减去这两个值。

TimeSpan duration = endUtc - startUtc;

如果您确实使用野田时间,UTC 值最好用Instant 表示,这样很容易获得Duration

Instant start = Instant.FromDateTimeUtc(startUtc);
Instant end = Instant.FromDateTimeUtc(endUtc);
Duration duration = end - start;

您也可以使用恰好是“UTC”的ZonedDateTime 值来表示它们,但是您很快会发现API 要求您将它们转换回Instant 值才能获得Duration

ZonedDateTime start = LocalDateTime.FromDateTime(startUtc).InUtc();
ZonedDateTime end = LocalDateTime.FromDateTime(endUtc).InUtc();
Duration duration = end.ToInstant() - start.ToInstant();

您可能认为只使用LocalDateTime 是一种选择,但该结构表示墙上时间,没有任何时区信息。您无法在其中两个之间获得Duration。您可以使用Period.Between 获得Period,但这将代表两个表示之间的日历/时钟值差异 - 这与实际经过的时间量不同.

作为有助于理解差异的思考练习,请考虑以下两个值:

2015-11-01 00:30
2015-11-01 01:30

如果我告诉您这些值是 UTC,那么就有一小时的差异。但是,如果我告诉您这些是挂钟值并且它们位于美国东部时区,那么它们可能相隔 小时,或者它们可能是 小时分开。这取决于 01:30 是 DST 转换之前的时间,还是之后的时间 - 因为这一天有两个时间。

现在如果我给你这些值:

2015-11-01 00:30
2015-11-01 02:30

同样,如果您将它们解释为 UTC,它们恰好相隔两个小时。但是,如果您在同一美国东部时区解释它们,那么它们正好相隔 三个 小时,因为该范围包括 DST 转换。如果你只是减去当地的挂墙时间值,那么你会得到两个小时,这是不正确的。

【讨论】:

  • 感谢您的详细回复,马特。我实际上是在那个时区的“日历/时钟值”差异之后。所以Period 版本就是我的选择。如果我正确理解了您的回复,那您也会在这种情况下使用吗?
  • 是的,这就是我要使用的。您的答案具有针对该特定问题的正确实现。尽管我对想要这样做的用例持怀疑态度。我希望你不会根据结果支付员工工资。 :o
【解决方案2】:

切换到使用ZonedDateTimeLocalDateTime 属性允许比较相对于时区的日期/时间。这适用于两个主要测试用例(23 小时和 25 小时):

public static double GetDurationForTimezone(DateTime startUtc, DateTime endUtc, string timezoneId)
{
    var timezone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(timezoneId);

    // convert UTC to timezone
    var startInstantUtc = Instant.FromDateTimeUtc(startUtc);
    var startZonedDateTime = startInstantUtc.InZone(timezone);
    var startLocalDateTime = startZonedDateTime.LocalDateTime;

    var endInstantUtc = Instant.FromDateTimeUtc(endUtc);
    var endZonedDateTime = endInstantUtc.InZone(timezone);
    var endLocalDateTime = endZonedDateTime.LocalDateTime;

    return Period.Between(startLocalDateTime, endLocalDateTime, PeriodUnits.Seconds).Seconds;
}

【讨论】:

  • 这得到了两个墙时间之间的差异,而不是它们之间经过的持续时间。它隐藏 DST 差距或重叠,而不是考虑它。
【解决方案3】:

学习本页:ZonedDateTime.Comparer Members

看来您必须使用属性 Local 而不是 Instant 来反映当地的夏令时。

【讨论】:

  • 感谢 Gustav,看来这为我指明了正确的方向。将添加一个答案。
猜你喜欢
  • 1970-01-01
  • 2012-04-18
  • 1970-01-01
  • 1970-01-01
  • 2012-02-04
  • 1970-01-01
  • 2012-10-29
  • 2016-08-16
  • 1970-01-01
相关资源
最近更新 更多