Ste*_*fan 4 .net c# datetime localtime dst
我担心我不明白.Net的DateTime
类如何处理本地时间戳(我住在德国,所以我的语言环境是de_DE).也许有人可以启发我一点;-)
该DateTime
构造可以用一年被调用,一个月等参数.另外,可以提供DateTimeKind
值,,或(=默认值).Local
Utc
Unspecified
例:
DateTime a = new DateTime(2015, 03, 29, 02, 30, 00, DateTimeKind.Local);
DateTime b = new DateTime(2015, 03, 29, 02, 30, 00, DateTimeKind.Utc);
DateTime c = new DateTime(2015, 03, 29, 02, 30, 00, DateTimeKind.Unspecified);
DateTime d = new DateTime(2015, 03, 29, 02, 30, 00);
Run Code Online (Sandbox Code Playgroud)
根据定义,值c和d是相同的.但如果我将所有四个相互比较,那么所有四个都是相同的.检查VS调试器中的对象会显示所有Ticks
值(InternalTicks
以及相同).但是,内部dateData
值不同,但比较运算符显然会忽略它们.
您可能已经注意到,我在今年3月29日02:30 AM构建了一个值.这个时刻在我们的时区中不存在,因为切换到夏令时会跳过它.所以我原本期望得到构造对象的异常a
,但这并没有发生.
此外,DateTime
还有一种方法ToUniversalTime()
可将解释为本地时间的值转换为等效的UTC值.为了测试,我按如下方式运行循环:
DateTime dt = new DateTime(2015, 03, 29, 01, 58, 00, DateTimeKind.Local);
DateTime dtEnd = new DateTime(2015, 03, 29, 03, 03, 00, DateTimeKind.Local);
while (dt < dtEnd)
{
Log(" Localtime " + dt + " converted to UTC is " + dt.ToUniversalTime());
dt = dt.AddMinutes(1);
}
Run Code Online (Sandbox Code Playgroud)
结果是:
Localtime 29.03.2015 01:58:00 converted to UTC is 29.03.2015 00:58:00
Localtime 29.03.2015 01:59:00 converted to UTC is 29.03.2015 00:59:00
Localtime 29.03.2015 02:00:00 converted to UTC is 29.03.2015 01:00:00
Localtime 29.03.2015 02:01:00 converted to UTC is 29.03.2015 01:01:00
Localtime 29.03.2015 02:02:00 converted to UTC is 29.03.2015 01:02:00
...
Localtime 29.03.2015 02:58:00 converted to UTC is 29.03.2015 01:58:00
Localtime 29.03.2015 02:59:00 converted to UTC is 29.03.2015 01:59:00
Localtime 29.03.2015 03:00:00 converted to UTC is 29.03.2015 01:00:00
Localtime 29.03.2015 03:01:00 converted to UTC is 29.03.2015 01:01:00
Localtime 29.03.2015 03:02:00 converted to UTC is 29.03.2015 01:02:00
Run Code Online (Sandbox Code Playgroud)
因此,.Net将不存在的时间戳从本地时间转换为UTC没有问题.此外,向现有本地时间戳添加分钟不是本地感知的,并且提供不存在的时间戳.
因此,在转换后,添加64个单分钟会产生比以前大4分钟的UTC时间戳.
换句话说,在本地时间和UTC之间进行转换应该是双射,在合法时间戳值之间给出一对一的对应关系.
简而言之:如何正确处理预期的方式(根据.Net)?DateTimeKind
如果没有正确考虑,有什么感觉?我甚至不敢问如何处理闰秒(23:59:60);-)
是的,.NET 中的 DateTime 类型非常混乱,正如您所观察到的,因为它不支持时区、多个日历和许多其他有用的概念(例如间隔等)的概念。
更好一点的是DateTimeOffset类型,它添加了时区偏移信息。DateTimeOffset 将允许您更准确地表示您在问题中显示的时间,并且比较将考虑时区偏移。但这种类型也并不完美。它仍然不支持真实的时区信息,仅支持偏移量。因此无法执行复杂的 DST 计算或支持高级日历。
要获得更彻底的解决方案,您可以使用NodaTime
迈克的回答很好.是的,DateTimeOffset
几乎总是被优先考虑DateTime
(但不适用于所有情况),并且Noda Time在许多方面都非常优越.但是,我可以添加更多细节来解决您的问题和观察.
首先,MSDN有这样的说法:
UTC时间适用于计算,比较以及在文件中存储日期和时间.本地时间适合在桌面应用程序的用户界面中显示.时区感知应用程序(例如许多Web应用程序)也需要与许多其他时区一起使用.
...
时区之间的转换操作(例如在UTC和本地时间之间,或在一个时区和另一个时区之间)将夏令时考虑在内,但算术和比较操作则不会.
由此我们可以得出结论,您提供的测试无效,因为它使用本地时间执行计算.它之所以有用,是因为它突出了API如何允许您打破自己的文档指南.通常,由于在该日期当地时区中从02:00到03:00之前的时间不存在,除非是在数学上获得,否则在现实世界中不可能遇到,例如每天复发没有考虑DST的模式.
顺便说一句,Noda Time解决这个问题的部分是在通过该方法ZoneLocalMappingResolver
转换LocalDateTime
为a时ZonedDateTime
使用的部分localDateTime.InZone
.有一些合理的默认值,例如InZoneStrictly
,或者InZoneLeniently
,它不仅仅是像你所说的那样默默地移动DateTime
.
关于你的断言:
换句话说,在本地时间和UTC之间进行转换应该是双射,在合法时间戳值之间给出一对一的对应关系.
实际上,这不是一个双射.(根据维基百科上的双射定义,它不满足标准3或4.)只有UTC到本地方向的转换才是一个函数.在本地到UTC方向的转换在弹簧前向DST转换期间具有不连续性,并且在后退DST转换期间具有模糊性.您可能希望查看DST标记维基中的图表.
回答您的具体问题:
如何正确处理预期的方式(根据.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));
}
Run Code Online (Sandbox Code Playgroud)
如果没有正确考虑DateTimeKind有什么意义?
原来,DateTime
没有一种.它表现得好像没有指定那种. DateTimeKind
在.NET 2.0中添加了.
它涵盖的主要用例是防止双重转换.例如:
DateTime result = DateTime.UtcNow.ToUniversalTime();
Run Code Online (Sandbox Code Playgroud)
要么
DateTime result = DateTime.Now.ToLocalTime();
Run Code Online (Sandbox Code Playgroud)
在.NET 2.0之前,这些都会导致错误数据,因为ToUniversalTime
和ToLocalTime
方法必须假设输入值未被转换.它会盲目地应用时区偏移,即使该值已经在所需的时区内.
还有一些其他边缘情况,但这是主要情况.此外,存在隐藏的第四种类型,其使用使得在回退转换期间以下仍将保持具有模糊值.
DateTime now = DateTime.Now;
Assert.True(now.ToUniversalTime().ToLocalTime() == now);
Run Code Online (Sandbox Code Playgroud)
Jon Skeet有一篇关于此的博文,你现在也可以在.NET Reference源代码或新的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
Run Code Online (Sandbox Code Playgroud)
我正在午夜2点钟显示同步,但实际上可能要晚得多.时钟同步始终发生,而不仅仅是闰秒.计算机的本地时钟不是超精密仪器 - 它会漂移,并且必须定期进行校正.你不能假设当前时间总是单调增加 - 它可以向前跳,或向后跳.
此外,上面的图表并不完全准确.我在几秒钟内表现出了硬转换,但实际上OS通常会通过在几秒钟的较长时间内(一次几毫秒)分散几个亚秒增量的变化效果来引入微小的修正.
在API级别,所有API都不会在秒字段中支持超过59个.如果他们完全支持它,它可能只是在解析期间.
DateTime.Parse("2015-06-30T23:59:60Z")
Run Code Online (Sandbox Code Playgroud)
这将抛出异常.如果是工作,那就得第二Munge时间额外的飞跃,要么返回先前的第二(2015-06-30T23:59:59Z
),或在下一秒(2015-07-01T00:00:00Z
).