净日期时间与本地时间和夏令时 [英] .Net DateTime with local time and DST

查看:295
本文介绍了净日期时间与本地时间和夏令时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我怕我真的不明白。NET的的DateTime 类是如何处理本地时间戳(我住在德国,所以我的语言环境是de_DE这个)。也许有人可以赐教一点; - )

的DateTime 的构造函数可以调用与年份,月份等参数。此外本地 UTC ,或一个 DateTimeKind 未指定(=默认值)可以提供。

例如:

  DateTime的一个=新的日期时间(2015,03,29,02,30,00,DateTimeKind.Local);
日期时间B =新的日期时间(2015,03,29,02,30,00,DateTimeKind.Utc);
日期时间C =新的日期时间(2015,03,29,02,30,00,DateTimeKind.Unspecified);
日期时间D =新的日期时间(2015,03,29,02,30,00);
 

根据定义,值c和d是相同的。但是,如果我比较所有针对对方,所有四个是相同的。检查在VS的调试器中的对象显示,值( InternalTicks 以及)是相同的所有。然而,内部 dateData 值是不同的,但显然忽视了比较操作。

正如你可能已经注意到,我今年,凌晨02:30构建了3月29日的值。这个时刻不会在我们的时区存在,因为它是通过切换到夏令时跳过。所以,我希望得到一个异常构造对象 A ,但是这并没有发生。

此外,的DateTime 有一个方法 ToUniversalTime()可以转换的值是除preTED为本地时间对应的UTC值。为了测试,我跑了一个循环,如下所示:

  DateTime的DT =新的日期时间(2015,03,29,01,58,00,DateTimeKind.Local);
日期时间DTEND =新的日期时间(2015,03,29,03,03,00,DateTimeKind.Local);
而(DT< D​​TEND)
{
    日志(本地时间+ DT +转换为UTC是+ dt.ToUniversalTime());
    dt的= dt.AddMinutes(1);
}
 

结果是:

 本地时间2015年3月29日1点58分00秒转换为UTC是2015年3月29日0时58分00秒
本地时间2015年3月29日1时59分零零秒转换为UTC是2015年3月29日00:59:00
本地时间2015年3月29日02:00:00转变为UTC是2015年3月29日01:00:00
本地时间2015年3月29日2点01分○○秒转换为UTC是2015年3月29日1时01分00秒
本地时间2015年3月29日2时02分00秒转换为UTC是2015年3月29日1时02分○○秒
...
本地时间2015年3月29日二时58分00秒转换为UTC是2015年3月29日一点58分00秒
本地时间2015年3月29日2时59分00秒转换为UTC是2015年3月29日1时59分00秒
本地时间2015年3月29日03:00:00转变为UTC是2015年3月29日01:00:00
本地时间2015年3月29日3点01分00秒转换为UTC是2015年3月29日1时01分00秒
本地时间2015年3月29日3时○二分00秒转换为UTC是2015年3月29日1时02分○○秒
 

所以,净有没有问题,将不存在时间戳从本地时间为UTC。此外,加入一分钟到一个现有的本地时间戳不是本地的感知,并给出了一个不存在的时间戳。

其结果是,加入64单分钟产量,转换,UTC时间戳比以前更大的仅4分钟后

在换言之,本地时间和UTC之间的转换应是一个双射,给合法时间戳值之间的一对一的对应

要短切一个漫长的故事:我该如何处理这个正确的预期方式(根据.NET)?有意识什么是 DateTimeKind 如果不考虑是否正确?我甚至都不敢问闰秒(23:59:60)如何处理; - )

解决方案

迈克的回答是不错的。是的,的DateTimeOffset 几乎总是$ pfered了的DateTime 点$(但不包括的所有的场景)和野田佳彦时间是大大优于在许多方面。不过,我可以添加一些更多的细节来解决你的问题和意见。

首先, MSDN有这样一段话

  

UTC时间是适合的计算,比较以及存储日期和时间中的文件。当地时间是适合于桌面应用程序的用户界面显示。时区的应用程序(如许多Web应用程序)也需要与一些其它时区工作。

     

...

     

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

由此我们可以得出结论,您所提供的测试是无效的,因为它执行使用本地时间计算。重要的是,它强调如何API允许你打破自己的记录准则仅是很有用的。在一般情况下,由于从02:00在该日期本地时区不存在时,以3点之前,它除非它被数学上得到,例如通过每日复发不太可能在现实世界中可能遇到图案没有考虑DST考虑。

顺便说一句,野田的时间,解决这是 ZoneLocalMappingResolver ,这是在转换时使用了 LocalDateTime ZonedDateTime 通过 localDateTime.InZone 方法。有一些合理的默认值,例如 InZoneStrictly InZoneLeniently ,但它不只是默默的转向像你这样的说明与的DateTime

对于你的说法:

  

在换言之,本地时间和UTC之间的转换应是一个双射,给合法时间戳值之间的一对一的对应

其实,这不是一个双射。 (通过维基百科的双射的定义,不符合标准的3或4)只有转换的UTC到当地的方向是一个函数。转换在当地对UTC方向在春天进DST转换过程中的不连续性,而退一步DST转换过程中有歧义。您不妨审查在DST标签维基图表

要回答你的具体问题:

  我

如何处理这个正确的预期方式(根据.NET)?

  DateTime的DT =新的日期时间(2015,03,29,01,58,00,DateTimeKind.Local);
日期时间DTEND =新的日期时间(2015,03,29,03,03,00,DateTimeKind.Local);

//我在这里把这个如果你想用不同的时区工作
的TimeZoneInfo TZ = TimeZoneInfo.Local; //你会在这里更改此变量

//创建的DateTimeOffset包装,以便补偿不迷路
的DateTimeOffset DTO =新的DateTimeOffset(DT,tz.GetUtcOffset(DT));
的DateTimeOffset dtoEnd =新的DateTimeOffset(DTEND,tz.GetUtcOffset(DTEND));

//或者,如果你只是用本地时区的工作,你可以使用
//这个构造函数,它假设TimeZoneInfo.Local
//的DateTimeOffset DTO =新的DateTimeOffset(DT);
//的DateTimeOffset dtoEnd =新的DateTimeOffset(DTEND);

而(DTO< dtoEnd)
{
    日志(本地时间+ DTO +转换为UTC是+ dto.ToUniversalTime());

    //数学与的DateTimeOffset是安全的,瞬时间,
    //但它可能不会让你在当地所希望的时间偏移。
    DTO = dto.AddMinutes(1);

    //可能会在局部区域改变的偏移量。
    //通过任一调整了以下(具有相同效果)。
    DTO = TimeZoneInfo.ConvertTime(DTO,TZ);
    // DTO = dto.ToOffset(tz.GetUtcOffset(DTO));
}
 

  

具有DateTimeKind感这是如果它不考虑正确?

本来,的DateTime 没有一个样。它的表现就好像一种是不确定的。 DateTimeKind 中添加了.NET 2.0。

主要用例覆盖是prevent双转换。例如:

  DateTime的结果= DateTime.UtcNow.ToUniversalTime();
 

  DateTime的结果= DateTime.Now.ToLocalTime();
 

.NET 2.0之前,这两个都会导致错误的数据,因为 ToUniversalTime ToLocalTime 方法必须作一个假设,输入值的没有的转换。它会盲目地应用时区偏移,即使当值的已经的在所需的时区。

有一些其他的边缘情况,但是这是主要的一个。此外,还有一个隐藏的第四的一种,它是用来使得下列仍将占据了暧昧值回退过渡时期。

 的DateTime现在= DateTime.Now;
Assert.True(now.ToUniversalTime()ToLocalTime()==了。);
 

乔恩斯基特有一个谈好博客文章这,你现在也可以看到它在评论中讨论在.NET参考源的或<一href="https://github.com/dotnet/coreclr/blob/b1dfb9747869828bfa8aee271a359bb6c689b1d0/src/mscorlib/src/System/DateTime.cs#L38-L42"相对=nofollow>新coreclr来源。

  

我也不敢问闰秒(23:59:60)如何处理; - )

闰秒,实际上不支持.NET可言,包括野田时的当前版本。他们也不会支持任何的Win32 API,也没有你永远不会看到在Windows钟闰秒。

在Windows中,闰秒是通过NTP同步应用。时钟滴答由仿佛闰秒没有发生,并在其下一个时钟同步,时间调整,它被吸收。这里是下一个闰秒会是这样的:

现实世界的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&LT; - 落后一秒 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&LT; - NTP同步 2015-07-01T00:00:03Z 2015-07-01T00:00:03Z

我展示在2秒午夜同步,但它可能真的要晚得多。时钟同步发生的时间,不只是在闰秒。计算机的本地时钟是一个不超precise仪器 - 它会漂移,并定期必须被纠正。你不能假设的当前时间总是单调递增的 - 它可以快进或向后跳

另外,上面的图表是不完全准确的。我发现在几秒钟​​内很难转变,但在现实中的操作系统往往会推出少量修正为S preading了超过几秒钟的较长时期的几个子秒的增量变化的影响(几毫秒的时间)。

目前的API级别,没有任何的API将支持超过59的秒域。如果他们的的,以支持它的话,那很可能只是在分析过程中。

  DateTime.Parse(2015-06-30T23:59:60Z)
 

这会抛出异常。如果它的的工作,那就得第二munge额外的飞跃,无论是第二次返回previous( 2015-06-30T23:59:59Z ),或下一秒钟( 2015-07-01T00:00:00Z

I'm afraid I don't really understand how .Net's DateTime class handles local timestamps (I live in Germany, so my locale is de_DE). Perhaps someone can enlighten me a bit ;-)

The DateTime constructor can be called with year, month etc. parameters. Additionally a DateTimeKind value of Local, Utc, or Unspecified (=default) can be provided.

Example:

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);

Per definition, values c and d are identical. But if I compare all against each other, all four are identical. Inspecting the objects in VS's debugger shows that the Ticks value (and InternalTicks as well) is the same for all. However, internal dateData values are different, but are obviously ignored by the comparison operator.

As you might have noticed, I constructed a value for March 29th this year, 02:30 AM. This moment in time does not exist in our timezone as it is skipped by switching to Daylight Saving Time. So I had expected to get an exception for constructing object a, but this did not happen.

Furthermore, DateTime has a method ToUniversalTime() that converts a value that is interpreted as local time to the equivalent UTC value. For testing, I ran a loop as follows:

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);
}

The result is:

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

So, .Net has no problem converting non-existing timestamps from local time to UTC. Also, adding a minute to an existing local timestamp is not local-aware and gives a non-existing timestamp.

As a result, adding 64 single minutes yields, after conversion, a UTC timestamp that is only 4 minutes larger than before.

In other words, converting between local time and UTC should be a bijection, giving a one-to-one correspondence between legal timestamp values.

To cut a long story short: How do I handle this correctly the intended way (according to .Net)? What is the sense of having DateTimeKind if it is not taken into account correctly? I don't even dare to ask how leap seconds (at 23:59:60) are handled ;-)

解决方案

Mike's answer is good. Yes, DateTimeOffset is almost always prefered over DateTime (but not for all scenarios), and Noda Time is vastly superior in many regards. However, I can add some more details to address your questions and observations.

First, MSDN has this to say:

UTC time is suitable for calculations, comparisons, and storing dates and time in files. Local time is appropriate for display in user interfaces of desktop applications. Time zone-aware applications (such as many Web applications) also need to work with a number of other time zones.

...

Conversion operations between time zones (such as between UTC and local time, or between one time zone and another) take daylight saving time into account, but arithmetic and comparison operations do not.

From this we can conclude that the test you provided is not valid because it performs calculations using a local time. It is useful only in that it highlights how the API allows you to break its own documented guidelines. In general, since the time from 02:00 to just before 03:00 does not exist in the local time zone on that date, it's not likely to be encountered in the real world unless it was obtained mathematically, such as by a daily recurrence pattern that didn't take DST into account.

BTW, the part of Noda Time that addresses this is the ZoneLocalMappingResolver, which is used when converting a LocalDateTime to a ZonedDateTime via the localDateTime.InZone method. There are some reasonable defaults such as InZoneStrictly, or InZoneLeniently, but it's not just silently shifted like you illustrated with DateTime.

With regard to your assertion:

In other words, converting between local time and UTC should be a bijection, giving a one-to-one correspondence between legal timestamp values.

Actually, it's not a bijection. (By the definition of bijection on Wikipedia, it does not satisfy criteria 3 or 4.) Only conversion in the UTC-to-local direction is a function. Conversion in the local-to-UTC direction has a discontinuity in during the spring-forward DST transition, and has ambiguity during the fall-back DST transition. You may wish to review the graphs in the DST tag wiki.

To answer your specific questions:

How do I handle this correctly the intended way (according to .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));
}

What is the sense of having DateTimeKind if it is not taken into account correctly?

Originally, DateTime didn't have a kind. It behaved as if the kind was unspecified. DateTimeKind was added in .NET 2.0.

The main use case it covers is to prevent double conversion. For example:

DateTime result = DateTime.UtcNow.ToUniversalTime();

or

DateTime result = DateTime.Now.ToLocalTime();

Before .NET 2.0, these would both result in bad data, because the ToUniversalTime and ToLocalTime methods had to make the assumption that the input value was not converted. It would blindly apply the time zone offset, even when the value was already in the desired time zone.

There are a few other edge cases, but this is the main one. Also, there is a hidden fourth kind, which is used such that the following will still hold up with ambiguous values during the fall-back transition.

DateTime now = DateTime.Now;
Assert.True(now.ToUniversalTime().ToLocalTime() == now);

Jon Skeet has a good blog post about this, and you can also now see it discussed in the comments in the .NET Reference sources or in the new coreclr sources.

I don't even dare to ask how leap seconds (at 23:59:60) are handled ;-)

Leap seconds are actually not supported by .NET at all, including the current version of Noda Time. They're also not supported by any of the Win32 APIs, nor will you ever observe a leap second on the Windows clock.

In Windows, leap seconds are applied via NTP synchronization. The clock ticks by as if the leap second didn't occur, and during its next clock sync, the time is adjusted and it is absorbed. Here's what the next leap second will look like:

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

I'm showing the sync at 2 seconds past midnight, but it could really be much later. Clock sync happens all the time, not just at leap seconds. The computer's local clock is not an ultra-precise instrument - it will drift, and periodically has to be corrected. You cannot assume the current time is always monotonically increasing - it could skip forward, or jump backward.

Also, the chart above isn't exactly accurate. I showed a hard shift in seconds, but in reality the OS will often introduce minor corrections by spreading out the effect of the change in several sub-second increments over a longer period of several seconds (a few milliseconds at a time).

At an API level, none of the APIs will support more than 59 in a seconds field. If they were to support it at all, it would probably just be during parsing.

DateTime.Parse("2015-06-30T23:59:60Z")

This will throw an exception. If it were to work, it would have to munge the extra leap second and either return the previous second (2015-06-30T23:59:59Z), or the next second (2015-07-01T00:00:00Z).

这篇关于净日期时间与本地时间和夏令时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆