【问题标题】:Round-trip conversion of a System.DateTime instance via a Unix time stamp is off by 1 hour通过 Unix 时间戳对 System.DateTime 实例的往返转换延迟 1 小时
【发布时间】:2019-01-29 01:41:26
【问题描述】:

注意:为方便起见,PowerShell 用于演示该行为,但问题是System.DateTime .NET 类型与System.DateTimeOffset 类型相比的令人惊讶的行为。

这种行为可能有一个很好的概念性原因,但我没有意识到。 如果有,了解为什么以及如何避免这个陷阱会很有帮助。

以下 PowerShell sn-p 演示了 DateTime 实例通过其 Unix 时间等价物以本地时间表示的往返转换:

# Get midnight 1 Jul 2018 in local time.
$date = Get-Date '2018-07-01'

# Convert to Unix time (seconds since midnight 1 Jan 1970 UTC)
# Note: In PowerShell Core this command could be simplified to: Get-Date -Uformat %s $date
$unixTime = [int] (Get-Date -Uformat %s $date.ToUniversalTime())

# Reconvert the Unix time stamp to a local [datetime] instance.
# Note that even though the input string is a UTC time, the cast creates
# a *local* System.DateTime instance (.Kind equals Local)
$dateFromUnixTime1 = ([datetime] '1970-01-01Z').AddSeconds($unixTime)

# Reconvert the Unix time stamp to a local [datetime] instance via 
# a [System.DateTimeOffset] instance:
$dateFromUnixTime2 = ([datetimeoffset ] '1970-01-01Z').AddSeconds($unixTime).LocalDateTime

# Output the results
@"
original:                           $date
Unix time:                          $unixTime
reconstructed via [datetime]:       $dateFromUnixTime1
reconstructed via [datetimeoffset]: $dateFromUnixTime2
"@

以上产量(在我的美式英语系统上Eastern Timezone):

original:                           07/01/2018 00:00:00
Unix time:                          1530417600
reconstructed via [datetime]:       06/30/2018 23:00:00
reconstructed via [datetimeoffset]: 07/01/2018 00:00:00

如您所见,通过([datetime] '1970-01-01Z') 实例获得的[datetime] 实例(其.Kind 值为Local,即本地 日期)已关闭1 小时,而基于 [datetimeoffset] 的计算(基于 UTC)按预期工作。

我怀疑这与 DST(夏令时)有关 - 例如,2018-12-01 不会发生这种情况 - 但我不清楚为什么。

【问题讨论】:

    标签: .net datetime .net-core datetimeoffset date-math


    【解决方案1】:

    最终问题是由于AddSeconds 被基于本地时间的DateTime 调用。 The .net docs 说(强调我的):

    时区之间的转换操作(例如 UTC 和本地时间之间,或一个时区和另一个时区之间)会考虑夏令时,但算术和比较操作不考虑


    我不是 PowerShell 专家,但似乎 [datetime] 'somestring' 相当于调用 DateTime.Parse("somestring")。使用该 API,默认行为是根据本地时区返回值。由于您传递了Z,因此输入被视为UTC,然后将值转换为本地时间。这就是造成差异的原因。

    在 C# 中(继续使用 DateTime)可以传递参数来控制解析和输出行为:

    DateTime.Parse("1970-01-01Z", CultureInfo.InvariantCulture, DateTimeStyles.RoundTripKind)
    

    RoundTripKind 样式(部分)表示输出类型应由输入字符串中的信息确定。由于 Z 表示 UTC,因此您将在输出中获得基于 UTC 的 DateTime

    我不确定如何将这些参数传递到powershell中的速记(类型加速器?),但速记是这样的:

    [datetime]::Parse('1970-01-01Z', [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::RoundtripKind)
    

    另外,您可以通过使用内置方法而不是解析来使事情变得更容易:

    DateTimeOffset.FromUnixTimeSeconds(unixTime)
    

    如果您愿意,您可以关闭 DateTime(但请记住,DateTimeOffset.UtcDateTime 保留 UTC 类型,而 DateTimeOffset.DateTime 将始终具有未指定的类型,而 DateTimeOffset.LocalDateTime 返回本地类型)。

    我认为的powershell应该是这样的:

    [datetimeoffset]::FromUnixTimeSeconds($unixTime).UtcDateTime
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-15
      • 1970-01-01
      • 1970-01-01
      • 2015-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-07
      相关资源
      最近更新 更多