DateTime是否相对于ToUniversalTime和ToLocalTime往返?

时间:2013-06-04 11:39:05

标签: .net datetime time timezone

在下面的代码片段中,我声称保证为任何DateTime值保留的往返属性是什么?

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

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

可能的边缘情况是时区,夏令时,闰秒,无法代表或模糊的当地时间,......

2 个答案:

答案 0 :(得分:5)

确实如此,但只是通过引擎盖下的一些黑暗魔法hackery。

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

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

当然,在.Net之外,DateTime Local种类并不是往返旅行。它在返回时被视为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
    

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

    另外,我们使用的公历直到1582年才生效,所以1/1/0001是他们的参考点。 1582年之前的日期在我们目前的规模上毫无意义,必须进行翻译。

  • 字符串可以是传输日期和时间值的好方法,因为它们是人类可读的。但是你也应该确保它们是机器可读的,没有任何歧义。例如,请勿使用1/4/2013之类的值,因为如果没有其他文化信息,您将无法知道1月4日或4月1日。相反,请使用ISO8601格式之一。

    将这些用于DateTime时,您可以使用"o"格式字符串,该字符串可以往返该类型。它会为Z种添加Utc,或为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。它还将应用您提供的任何偏移量,以便结果是等效的 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值将始终往返,因为它们以序列化格式携带所有相关信息。 UnspecifiedUtcDateTime也是如此。只需避开LocalDateTime。那些很容易让你陷入困境。

请回答?

再次阅读本文,并在我提供了大量详细信息时意识到,我没有直接回答您的问题。如果输入类型已经已经转换为第一种类型,则测试将失败。让我们看看两个测试条件:

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

    如果原始值已经为Utc种类,则会失败。

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

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

    如果原始值已经为Local种类,则会失败。

另请注意,Kind属性不参与相等性检查。因此,虽然其中任何一个的输入可以是Unspecified,但测试1的输出将始终具有Local种类,并且测试2的输出将始终具有Utc种类 - < em>但测试仍将通过

答案 1 :(得分:1)

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;