迈克的回答很好。是的,DateTimeOffset
几乎总是优先于DateTime
(但不适合all场景),以及野田时间在很多方面都非常优越。不过,我可以添加更多详细信息来解决您的问题和意见。
First, MSDN有这样的说法:
UTC 时间适用于计算、比较以及在文件中存储日期和时间。本地时间适合显示在桌面应用程序的用户界面中。时区感知应用程序(例如许多 Web 应用程序)还需要与许多其他时区一起工作。
...
时区之间的转换操作(例如 UTC 与当地时间之间,或一个时区与另一个时区之间)会考虑夏令时,但算术和比较操作则不会。
由此我们可以得出结论,您提供的测试无效,因为它使用当地时间进行计算。它的有用之处仅在于它强调了 API 如何允许您打破其自己记录的指南。一般来说,由于该日期的本地时区不存在从 02:00 到 03:00 之前的时间,因此在现实世界中不太可能遇到,除非通过数学方式获得,例如通过每日循环获得未考虑 DST 的模式。
顺便说一句,野田时间解决这个问题的部分是ZoneLocalMappingResolver,在转换时使用LocalDateTime
to a ZonedDateTime
通过localDateTime.InZone
方法。有一些合理的默认值,例如InZoneStrictly
, or InZoneLeniently
,但它并不只是像你所展示的那样默默地转移DateTime
.
关于你的主张:
换句话说,本地时间和 UTC 之间的转换应该是双射,在合法时间戳值之间给出一一对应的关系。
事实上,这不是双射。 (经过维基百科上双射的定义,它不满足条件 3 或 4。)只有 UTC 到本地方向的转换才是函数。本地到 UTC 方向的转换在前向 DST 转换期间存在不连续性,并且在后退 DST 转换期间存在模糊性。您可能希望查看图表在 DST 标签 wiki 中.
回答您的具体问题:
我如何以预期的方式正确处理这个问题(根据.Net)?
DateTime dt = new DateTime(2015, 03, 29, 01, 58, 00, DateTimeKind.Local);
DateTime dtEnd = new DateTime(2015, 03, 29, 03, 03, 00, DateTimeKind.Local);
// I'm putting this here in case you want to work with a different time zone
TimeZoneInfo tz = TimeZoneInfo.Local; // you would change this variable here
// Create DateTimeOffset wrappers so the offset doesn't get lost
DateTimeOffset dto = new DateTimeOffset(dt, tz.GetUtcOffset(dt));
DateTimeOffset dtoEnd = new DateTimeOffset(dtEnd, tz.GetUtcOffset(dtEnd));
// Or, if you're only going to work with the local time zone, you can use
// this constructor, which assumes TimeZoneInfo.Local
//DateTimeOffset dto = new DateTimeOffset(dt);
//DateTimeOffset dtoEnd = new DateTimeOffset(dtEnd);
while (dto < dtoEnd)
{
Log(" Localtime " + dto + " converted to UTC is " + dto.ToUniversalTime());
// Math with DateTimeOffset is safe in instantaneous time,
// but it might not leave you at the desired offset by local time.
dto = dto.AddMinutes(1);
// The offset might have changed in the local zone.
// Adjust it by either of the following (with identical effect).
dto = TimeZoneInfo.ConvertTime(dto, tz);
//dto = dto.ToOffset(tz.GetUtcOffset(dto));
}
如果没有正确考虑,拥有 DateTimeKind 有何意义?
起初,DateTime
没有一种。它的表现就好像种类是未指定的。DateTimeKind
已在 .NET 2.0 中添加。
它涵盖的主要用例是防止双重转换。例如:
DateTime result = DateTime.UtcNow.ToUniversalTime();
or
DateTime result = DateTime.Now.ToLocalTime();
在 .NET 2.0 之前,这些都会导致错误数据,因为ToUniversalTime
and ToLocalTime
方法必须假设输入值是not转换。它会盲目地应用时区偏移,即使该值是already在所需的时区。
还有一些其他边缘情况,但这是主要的情况。另外,还有一个隐藏的fourthkind,它的使用使得以下内容在回退过渡期间仍能保持不明确的值。
DateTime now = DateTime.Now;
Assert.True(now.ToUniversalTime().ToLocalTime() == now);
乔恩·斯基特有关于此的一个很好的博客文章,您现在还可以在评论中看到它的讨论.NET 参考源 or in 新的 coreclr 源.
我什至不敢问闰秒(23:59:60)是如何处理的;-)
实际上.NET根本不支持闰秒,包括当前版本的Noda Time。它们也不被任何 Win32 API 支持,您也不会在 Windows 时钟上观察到闰秒。
在 Windows 中,闰秒通过 NTP 同步应用。时钟滴答作响,就好像闰秒没有发生一样,在下一次时钟同步期间,时间会被调整并被吸收。这是下一个闰秒的样子:
Real World Windows
-------------------- --------------------
2015-06-30T23:59:58Z 2015-06-30T23:59:58Z
2015-06-30T23:59:59Z 2015-06-30T23:59:59Z
2015-06-30T23:59:60Z 2015-07-01T00:00:00Z <-- one sec behind
2015-07-01T00:00:00Z 2015-07-01T00:00:01Z
2015-07-01T00:00:01Z 2015-07-01T00:00:02Z
2015-07-01T00:00:02Z 2015-07-01T00:00:02Z <-- NTP sync
2015-07-01T00:00:03Z 2015-07-01T00:00:03Z
我在午夜过后 2 秒显示同步,但实际上可能会晚得多。时钟同步始终发生,而不仅仅是闰秒。计算机的本地时钟不是超精密仪器 - 它会发生漂移,并且必须定期进行校正。您不能假设当前时间始终单调递增 - 它可以向前跳跃,也可以向后跳跃。
此外,上面的图表并不完全准确。我展示了几秒钟内的硬转变,但实际上,操作系统通常会通过在几秒的较长时间内(一次几毫秒)分散几个亚秒增量的变化效果来引入微小的修正。
在 API 级别,没有一个 API 支持超过 59 秒的字段。如果他们were要完全支持它,可能只是在解析过程中。
DateTime.Parse("2015-06-30T23:59:60Z")
这将引发异常。如果它were要工作,它必须处理额外的闰秒并返回前一秒(2015-06-30T23:59:59Z
),或者下一秒(2015-07-01T00:00:00Z
).