C# 带有本地时间和DST的.Net日期时间

C# 带有本地时间和DST的.Net日期时间,c#,.net,datetime,dst,localtime,C#,.net,Datetime,Dst,Localtime,恐怕我真的不明白.Net的DateTime类如何处理本地时间戳(我住在德国,所以我的语言环境是de_de)。也许有人能给我一点启发;-) 可以使用年、月等参数调用DateTime构造函数。此外,还可以提供Local、Utc或Unspecified(=默认值)的DateTimeKind值 例如: DateTime a = new DateTime(2015, 03, 29, 02, 30, 00, DateTimeKind.Local); DateTime b = new DateTime(201

恐怕我真的不明白.Net的
DateTime
类如何处理本地时间戳(我住在德国,所以我的语言环境是de_de)。也许有人能给我一点启发;-)

可以使用年、月等参数调用
DateTime
构造函数。此外,还可以提供
Local
Utc
Unspecified
(=默认值)的
DateTimeKind

例如:

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);
根据定义,值c和d是相同的。但是如果我把所有的东西相互比较,这四个都是一样的。在VS的调试器中检查对象表明
Ticks
值(以及
InternalTicks
值)对于所有对象都是相同的。但是,内部
dateData
值不同,但比较运算符显然会忽略这些值

正如您可能已经注意到的,我为今年3月29日凌晨02:30构建了一个值。这个时刻在我们的时区中并不存在,因为切换到夏令时会跳过它。所以我本来希望在构建对象
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);
}
因此,.Net可以将不存在的时间戳从本地时间转换为UTC。此外,向现有本地时间戳添加分钟不是本地感知的,而是提供一个不存在的时间戳

因此,在转换后,添加64分钟的UTC时间戳只比之前大4分钟

换句话说,本地时间和UTC之间的转换应该是双射,在合法的时间戳值之间提供一对一的对应关系


长话短说:我如何以预期的方式正确处理这个问题(根据.Net)?如果没有正确考虑,那么拥有
DateTimeKind
有什么意义?我甚至不敢问闰秒(23:59:60)是如何处理的;-)

是的,.NET中的DateTime类型非常混乱,正如您所观察到的,因为它不支持时区、多个日历和许多其他有用的概念,如间隔等

添加时区偏移量信息的类型稍好一些。DateTimeOffset将允许您更准确地表示问题中显示的时间,比较将考虑时区偏移。但这种类型也不完美。它仍然不支持真正的时区信息,只支持偏移量。因此,不可能执行复杂的DST计算或支持高级日历


要获得更彻底的解决方案,您可以使用

迈克的答案很好。是的,
DateTimeOffset
几乎总是比
DateTime
更受欢迎(但并非所有场景都是如此),而且在许多方面都非常优越。不过,我可以补充一些细节来回答您的问题和观察

首先:

UTC时间适用于计算、比较以及在文件中存储日期和时间。本地时间适合在桌面应用程序的用户界面中显示。时区感知应用程序(如许多Web应用程序)还需要与许多其他时区协同工作

时区之间的转换操作(如UTC和当地时间之间,或一个时区和另一个时区之间)考虑夏令时,但算术和比较操作不考虑夏令时

由此我们可以得出结论,您提供的测试无效,因为它使用本地时间执行计算。它之所以有用,只是因为它强调了API如何允许您打破它自己的文档化指导原则。一般来说,由于从02:00到03:00之前的时间在该日期的本地时区中不存在,因此在现实世界中不太可能遇到它,除非它是通过数学方法获得的,例如通过不考虑DST的每日重复模式

顺便说一句,Noda时间中解决此问题的部分是,当通过
LocalDateTime.InZone
方法将
LocalDateTime
转换为
ZonedDateTime
时,将使用该部分。有一些合理的默认值,例如
直接在区域内
,或
立即在区域内
,但它并不是像您用
日期时间
所说明的那样默默地移动

关于你的主张:

换句话说,本地时间和UTC之间的转换应该是双射,在合法的时间戳值之间提供一对一的对应关系

事实上,这不是双射。(根据,它不满足标准3或4。)只有UTC向本地方向的转换是一个函数。本地到UTC方向的转换在前弹力DST转换期间具有不连续性,在后弹力DST转换期间具有模糊性。您可能希望查看这些图表

要回答您的具体问题:

如何以预期的方式正确处理此问题(根据.Net)

在.NET 2.0之前,这两种方法都会导致不良数据,因为
ToUniversalTime
ToLocalTime
方法必须假设输入值未转换。它会盲目地应用时区偏移,即使该值已经在所需的时区中

还有一些其他的边缘情况,但这是主要的一个。此外,还有一种隐藏的第四种类型,使用该类型时,在回退转换期间,以下内容仍将保留不明确的值

DateTime now = DateTime.Now;
Assert.True(now.ToUniversalTime().ToLocalTime() == now);
Jon Skeet有,您现在也可以在中或中的评论中看到它的讨论

我甚至不敢问闰秒(23:59:60)是如何处理的;-)

NET实际上根本不支持闰秒,包括Noda Ti的当前版本
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));
}
DateTime result = DateTime.UtcNow.ToUniversalTime();
DateTime result = DateTime.Now.ToLocalTime();
DateTime now = DateTime.Now;
Assert.True(now.ToUniversalTime().ToLocalTime() == now);
DateTime.Parse("2015-06-30T23:59:60Z")