在下面的代码片段中,我声称保证为任何DateTime
值保留的往返属性是什么?
DateTime input = GetAnyDateTime();
DateTime roundtripped = input.ToUniversalTime().ToLocalTime();
Assert.IsTrue(input == roundtripped);
断言是否也适用于相反类型的往返(input.ToLocalTime().ToUniversalTime()
)?
可能的边缘情况是时区,夏令时,闰秒,无法代表或模糊的当地时间,......
答案 0 :(得分:5)
确实如此,但只是通过引擎盖下的一些黑暗魔法hackery。
当您查看DateTimeKind
时,您会看到三个选项,Unspecified
,Utc
和Local
。该信息被打包成内部64位表示的两位。由于有四个可以用两位表示的可能值,因此可以留下第四种空间。
正如Jon Skeet发现并描述in this blog post,确实隐藏了第四种!基本上,它是Local
,但在解决模糊时间时会有不同的对待。
当然,在.Net之外,DateTime
Local
种类并不是往返旅行。它在返回时被视为Unspecified
- 除非您另有说明。我在博客上写了here。更好的选择是DateTimeOffset
。
当然,这只是.Net中DateTime
的许多棘手问题之一。 Jon Skeet的另一篇精彩文章here讨论了其中的一些。
最佳解决方案是停止使用任何内置日期和时间类型,并熟悉Noda Time。在与其他系统交互时,您可能仍需要使用DateTime
或DateTimeOffset
,但您可以在内部使用Noda Time,并让它为您完成所有转换。
其他信息
您询问有关通过其他格式进行往返的问题,例如刻度线或字符串。
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
值将始终往返,因为它们以序列化格式携带所有相关信息。 Unspecified
和Utc
种DateTime
也是如此。只需避开Local
种DateTime
。那些很容易让你陷入困境。
请回答?
再次阅读本文,并在我提供了大量详细信息时意识到,我没有直接回答您的问题。如果输入类型已经已经转换为第一种类型,则测试将失败。让我们看看两个测试条件:
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;