3

在下面的代码片段中,我断言的往返属性是否可以保证任何DateTime值?

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

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

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

4

2 回答 2

7

确实如此,但只能通过引擎盖下的一些黑魔法黑客行为。

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

正如 Jon Skeet在这篇博文中发现和描述的那样,确实存在隐藏的第四种!基本上,它是Local但在解决模棱两可的时间时被区别对待。

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

当然,这只是DateTime.Net 中许多棘手的事情之一。Jon Skeet 的另一篇很棒的文章在这里讨论了其中的一些。

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

附加信息

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

  • DateTime.Ticks在 .Net 中不是一种很好的序列化格式,因为它不遵循单一的参考点。它们是自 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 午夜。每当您获得多个刻度时,例如 with .getTime(),它都会反映 UTC。您实际上无法通过简单的方法调用获得本地时间的刻度,因为它们在 JavaScript 中毫无意义。其他语言也是这样工作的。

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

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

    当使用这些时DateTime,您可以使用"o"格式字符串,它可以往返类型。它为种类附加一个ZUtc或为种类附加一个本地偏移量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. 但是,如果您有一个Z任何偏移量,那么默认情况下该种类将是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一致,RFC3339描述了 ISO8601 规范的这个配置文件,并且正在迅速成为在不同系统之间序列化时间戳的事实标准。恕我直言 - 您应该尽可能使用这种格式。它大大优于您在网络上常见的其他格式,例如 RFC1123。 以下是有关各种日期/时间格式的更多详细信息

DateTimeOffset值将始终往返,因为它们以序列化格式携带所有相关信息。所以会UnspecifiedUtcDateTime。只是避开Local各种DateTime. 这些很容易让你陷入困境。

请回答?

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

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

    如果原始值已经是Utckind,这将失败。

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

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

    如果原始值已经是Localkind,这将失败。

另请注意,该Kind物业不参与平等检查。因此,尽管其中任何一个的输入都可能是Unspecified,但测试 1 的输出将始终有一个Local种类,而测试 2 的输出将始终有一个Utc种类 -但无论如何测试都会通过

于 2013-06-04T15:31:36.123 回答
1

参考来源确认了与 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;
于 2015-10-09T05:41:26.740 回答