【问题标题】:Does DateTime roundtrip with respect to ToUniversalTime and ToLocalTime?DateTime 是否相对于 ToUniversalTime 和 ToLocalTime 往返?
【发布时间】:2013-05-30 18:58:12
【问题描述】:

在以下代码 sn-p 中,我断言的往返属性是否保证适用于任何 DateTime 值?

DateTime input = GetAnyDateTime();
DateTime roundtripped = input.ToUniversalTime().ToLocalTime();
Assert.IsTrue(input == roundtripped);

该断言是否也适用于反向类型的往返 (input.ToLocalTime().ToUniversalTime())?

可能的边缘情况包括时区、夏令时、闰秒、无法表示或不明确的当地时间……

【问题讨论】:

    标签: .net datetime time timezone


    【解决方案1】:

    确实如此,但只是通过一些隐藏在幕后的黑魔法黑客。

    当您查看DateTimeKind 时,您会看到三个选项,UnspecifiedUtcLocal。此信息被打包成内部 64 位表示的两位。由于有四个可能的值可以用两位表示,这就为第四种留下了空间。

    正如 Jon Skeet 发现并描述的 in this blog post确实存在隐藏的第四种!基本上,它是Local,但在解决模棱两可的时间时会有所不同。

    当然,在 .Net 之外,DateTimeLocal 无论如何都不会往返。它在退货时被视为Unspecified - 除非您另有说明。我在博客上写了这个here。更好的选择是DateTimeOffset

    当然,这只是 .Net 中 DateTime 的许多麻烦事之一。 Jon Skeet here 的另一篇很棒的帖子讨论了其中的一些。

    最好的解决方案是停止使用任何内置的日期和时间类型,而是熟悉Noda Time。在与其他系统交互时,您可能仍需使用DateTimeDateTimeOffset,但您可以在内部使用 Noda Time,让它为您完成所有转换。

    其他信息

    您询问了通过另一种格式(例如刻度或字符串)进行往返。

    • .Net 中的DateTime.Ticks 不是一个很好的序列化格式,因为它不遵循单一的参考点。它们是自 0001 年 1 月 1 日午夜以来 100 纳秒间隔的整数。但它们与 UTC 无关 - 相反,它们与正在使用的 Kind 对齐。换句话说:

      var utcNow = DateTime.UtcNow;
      var now = utcNow.ToLocalTime();
      var equal = utcNow.Ticks == now.Ticks; // false
      

      将其与使用 1970 年 1 月 1 日参考点的 JavaScript 进行比较 - UTC 午夜。每当您获得多个刻度时,例如 .getTime(),它反映的是 UTC。您实际上无法通过简单的方法调用获得本地时间的刻度,因为它们在 JavaScript 中毫无意义。其他语言也是如此。

      此外,我们使用的公历直到 1582 年才生效,所以 1/1/0001 是它们的参考点,这很奇怪。 1582 年之前的日期在我们目前的规模上是没有意义的,必须翻译。

    • 字符串是传输日期和时间值的好方法,因为它们是人类可读的。但是您还应该确保它们是机器可读的,没有任何歧义。例如,不要使用像 1/4/2013 这样的值,因为没有额外的文化信息,您将无法知道是 1 月 4 日还是 4 月 1 日。请改用其中一种 ISO8601 格式。

      将这些与DateTime 一起使用时,您可以使用"o" 格式字符串,它可以往返类型。它为Utc 种类附加一个Z,或为Local 种类附加一个本地偏移量。

      var dt = new DateTime(2013,6,4,8,56,0);  // Unspecified Kind
      var iso = dt.ToString("o");              // 2013-06-04T08:56:00.0000000
      
      var dt = DateTime.UtcNow;                // Utc Kind
      var iso = dt.ToString("o");              // 2013-06-04T15:56:00.0000000Z
      
      var dt = DateTime.Now;                   // Local Kind
      var iso = dt.ToString("o");              // 2013-06-04T08:56:00.0000000-07:00
      

      从这种格式解析时,如果你没有偏移量,那么类型将是Unspecified。但如果你有一个Zany 偏移量,那么默认情况下类型是Local。它还将应用您提供的任何偏移量,因此结果是等效的 local 时间。所以如果你想正确应用它,你必须明确告诉它来回那种类型。

      var dt = DateTime.Parse("2013-01-04T15:56:00.0000000Z");
      var kind = dt.Kind;  // Local - incorrect!
      var s = dt.ToString("o");  // "2013-01-04T08:56:00.0000000-07:00"  (ouch!)
      

      改为:

      var dt = DateTime.Parse("2013-01-04T15:56:00.0000000Z",
                              CultureInfo.InvariantCulture,
                              DateTimeStyles.RoundtripKind);
      var kind = dt.Kind;  // Utc  - that's better.
      var s = dt.ToString("o");  // "2013-01-04T15:56:00.0000000Z"  (nice!)
      

      当然,使用DateTimeOffset 会更好。当你以 ISO8601 格式序列化它时,你总是得到一个完整的表示:

      var dto = DateTimeOffset.Now;
      var iso = dto.ToString("o");   // 2013-06-04T08:56:00.0000000-07:00
      

      此格式与 RFC3339 一致,后者描述了 ISO8601 规范的此配置文件,并且正在迅速成为在不同系统之间序列化时间戳的事实标准。恕我直言 - 您应该尽可能使用这种格式。它大大优于您在网络上常见的其他格式,例如 RFC1123。 Here are some more details on various date/time formats.

    DateTimeOffset 值将始终往返,因为它们以序列化格式携带所有相关信息。 UnspecifiedUtc 类型的 DateTime 也是如此。只需避开LocalDateTime 之类的。这些很容易让你陷入困境。

    请回答?

    刚刚再读一遍,意识到虽然我提供了很多细节,但我并没有直接回答你的问题。如果输入类型已经属于您要转换的第一种类型,则测试将失败。我们来看两个测试条件:

    • someDateTime == someDateTime.ToUniversalTime().ToLocalTime()

      如果原始值已经是 Utc 种类,这将失败。

      如果在 DST 弹簧向前转换期间原始值在本地时区无效,则此测试也将失败。例如,2013-03-10 02:00:00 在美国太平洋时间不存在。但是,由于它不存在,您可能不会在数据中遇到它。所以它可能不是一个有效的测试条件。

    • someDateTime == someDateTime.ToLocalTime().ToUniversalTime()

      如果原始值已经是 Local 种类,这将失败。

    还要注意Kind 属性不参与相等性检查。因此,虽然其中任何一个的输入可能是 Unspecified,但测试 1 的输出将始终具有 Local 种类,而测试 2 的输出将始终具有 Utc 种类 - 但测试将还是通过

    【讨论】:

    • 很高兴知道。该信息适用于我的用例。但是,如果我要丢弃这些信息(可能是通过字符串表示或Ticks 来回传递)怎么办?
    • 谢谢。事实上,我的情况是一个风格为“o”的序列化。我发现在解析它时需要指定特殊选项很烦人(由于“z”后缀)。我希望得到一个不变的 DateTime 类型的 Utc 或 Unspecified 值。 DateTime.Parse 转换为本地,这就是我尝试使用 ToUniversalTime 的原因。这就是我的问题的来源。这似乎是一个功能设计缺陷。我看不出它应该这样做的任何理由。
    • 是的,非常有缺陷。所以很多人向微软抱怨过 DateTime,但他们更关心的是向后兼容性,而不是提供一个好的解决方案。进入 NodaTime - 它的学习曲线略高,但可以防止您陷入麻烦。
    • 当然,使用 NodaTime 的缺点是你有另一个依赖项。好处是它是由 Jon Skeet(和贡献者)编写的,所以质量是 A++
    【解决方案2】:

    reference source 确认链接到 Jon Skeet 的解释的已接受答案,可能会引起您的兴趣:

    //
    // There is also 4th state stored that is a special type of Local value that
    // is used to avoid data loss when round-tripping between local and UTC time.
    // See below for more information on this 4th state, although it is 
    // effectively hidden from most users, who just see the 3-state DateTimeKind
    // enumeration.
    [...]
    // For a description of various calendar issues, look at
    // 
    // Calendar Studies web site, at 
    

    // http://serendipity.nofadz.com/hermetic/cal_stud.htm.

    [...]
        // The data is stored as an unsigned 64-bit integeter
        //   Bits 01-62: The value of 100-nanosecond ticks where 0 represents 1/1/0001 12:00am, up until the value
        //               12/31/9999 23:59:59.9999999
        //   Bits 63-64: A four-state value that describes the DateTimeKind value of the date time, with a 2nd
        //               value for the rare case where the date time is local, but is in an overlapped daylight
        //               savings time hour and it is in daylight savings time. This allows distinction of these
        //               otherwise ambiguous local times and prevents data loss when round tripping from Local to
        //               UTC time.
        private UInt64 dateData;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-12-28
      • 2014-06-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多