为什么不减去两个本地DateTime值来说明夏令时? [英] Why doesn't subtracting two local DateTime values appear to account for Daylight Saving Time?

查看:67
本文介绍了为什么不减去两个本地DateTime值来说明夏令时?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用一些C#代码来尝试了解在夏令时方面C#中减去DateTime对象的工作原理。

I'm playing with some C# code to try to gain an understanding of how subtracting DateTime objects in C# works with respect to Daylight Saving Time.

每个Google和其他来源,2017年东部标准时区的夏令时春季提前事件发生在3月12日凌晨2:00。因此,当天的前几个小时是:

Per Google and other sources, the Daylight Saving Time "spring ahead" event in the Eastern Standard Time zone in 2017 was at 2:00am on March 12. So, the first few hours of the day on that date were:

   12:00am - 1:00am
    1:00am - 2:00am
   (There was no 2:00am - 3:00am hour due to the "spring ahead")
    3:00am - 4:00am

,如果我要计算该日期所在那个时区的1:00 am和4:00 am之间的时差,我希望结果为 2 小时。

So, if I were to calculate the time differential between 1:00am and 4:00am in that time zone on that date, I'd expect the result to be 2 hours.

但是,我拼凑起来用来模拟此问题的代码返回了 3 小时的时间跨度。

However, the code I put together to try to simulate this problem is returning a 3 hour TimeSpan.

代码:

TimeZoneInfo easternStandardTime = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");

DateTime oneAm = TimeZoneInfo.ConvertTime(new DateTime(2017, 03, 12, 01, 00, 00), easternStandardTime);
DateTime fourAm = TimeZoneInfo.ConvertTime(new DateTime(2017, 03, 12, 04, 00, 00), easternStandardTime);

TimeSpan difference = (fourAm - oneAm);

Console.WriteLine(oneAm);
Console.WriteLine(fourAm);
Console.WriteLine(TimeZoneInfo.Local.IsDaylightSavingTime(oneAm));
Console.WriteLine(TimeZoneInfo.Local.IsDaylightSavingTime(fourAm));
Console.WriteLine(difference);

在我的PC上,这会生成:

On my PC, this generates:

2017-03-12 01:00:00.000 -5
2017-03-12 04:00:00.000 -4
False
True
03:00:00

所有输出均符合预期-最终值3小时,如上所述,我希望改为2小时。

All of that output is as expected -- except that final value of 3 hours, which as I noted above, I would expect to be 2 hours instead.

很显然,我的代码没有正确模拟我所想到的情况。缺陷是什么?

Obviously, my code isn't correctly simulating the situation that I have in mind. What is the flaw?

推荐答案

观察:

// These are just plain unspecified DateTimes
DateTime dtOneAm = new DateTime(2017, 03, 12, 01, 00, 00);
DateTime dtFourAm = new DateTime(2017, 03, 12, 04, 00, 00);

// The difference is not going to do anything other than 4-1=3
TimeSpan difference1 = dtFourAm - dtOneAm;

// ... but we have a time zone to consider!
TimeZoneInfo eastern = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");

// Use that time zone to get DateTimeOffset values.
// The GetUtcOffset method has what we need.
DateTimeOffset dtoOneAmEastern = new DateTimeOffset(dtOneAm, eastern.GetUtcOffset(dtOneAm));
DateTimeOffset dtoFourAmEastern = new DateTimeOffset(dtFourAm, eastern.GetUtcOffset(dtFourAm));

// Subtracting these will take the offset into account!
// It essentially does this: [4-(-4)]-[1-(-5)] = 8-6 = 2
TimeSpan difference2 = dtoFourAmEastern - dtoOneAmEastern;

// Let's see the results
Console.WriteLine("dtOneAm: {0:o} (Kind: {1})", dtOneAm, dtOneAm.Kind);
Console.WriteLine("dtFourAm: {0:o} (Kind: {1})", dtFourAm, dtOneAm.Kind);
Console.WriteLine("difference1: {0}", difference1);

Console.WriteLine("dtoOneAmEastern: {0:o})", dtoOneAmEastern);
Console.WriteLine("dtoFourAmEastern: {0:o})", dtoFourAmEastern);
Console.WriteLine("difference2: {0}", difference2);

结果:

dtOneAm: 2017-03-12T01:00:00.0000000 (Kind: Unspecified)
dtFourAm: 2017-03-12T04:00:00.0000000 (Kind: Unspecified)
difference1: 03:00:00

dtoOneAmEastern: 2017-03-12T01:00:00.0000000-05:00)
dtoFourAmEastern: 2017-03-12T04:00:00.0000000-04:00)
difference2: 02:00:00

请注意, DateTime 在其 Kind 属性(即 DateTimeKind >未指定。它不属于任何特定时区。 DateTimeOffset 没有有一种类型,它具有 Offset ,它告诉您如何本地时间与UTC的时间差得很远。这些 都不给您时区。这就是 TimeZoneInfo 对象正在做的事情。请参见时区标签Wiki 中的时区!=偏移量。

Note that DateTime carries a DateTimeKind in its Kind property, which is Unspecified by default. It doesn't belong to any particular time zone. DateTimeOffset doesn't have a kind, it has an Offset, which tells you how far that local time is offset from UTC. Neither of these give you the time zone. That is what TimeZoneInfo object is doing. See "time zone != offset" in the timezone tag wiki.

我认为您可能会感到沮丧的部分是,由于一些历史原因, DateTime 对象永远不会理解时区进行数学运算,即使您可能拥有 DateTimeKind.Local 可以实现该功能以观察本地时区的转换,但是并没有这样做。

The part I think you are perhaps frustrated with, is that for several historical reasons, the DateTime object does not ever understand time zones when doing math, even when you might have DateTimeKind.Local. It could have been implemented to observe the transitions of the local time zone, but it was not done that way.

您可能还会感兴趣在 Noda Time 中,它以更加明智和有目的的方式为.NET中的日期和时间提供了非常不同的API 。

You also might be interested in Noda Time, which gives a very different API for date and time in .NET, in a much more sensible and purposeful way.

using NodaTime;

...

// Start with just the local values.
// They are local to *somewhere*, who knows where?  We didn't say.
LocalDateTime ldtOneAm = new LocalDateTime(2017, 3, 12, 1, 0, 0);
LocalDateTime ldtFourAm = new LocalDateTime(2017, 3, 12, 4, 0, 0);

// The following won't compile, because LocalDateTime does not reference
// a linear time scale!
// Duration difference = ldtFourAm - ldtOneAm;

// We can get the 3 hour period, but what does that really tell us?
Period period = Period.Between(ldtOneAm, ldtFourAm, PeriodUnits.Hours);

// But now lets introduce a time zone
DateTimeZone eastern = DateTimeZoneProviders.Tzdb["America/New_York"];

// And apply the zone to our local values.
// We'll choose to be lenient about DST gaps & overlaps.
ZonedDateTime zdtOneAmEastern = ldtOneAm.InZoneLeniently(eastern);
ZonedDateTime zdtFourAmEastern = ldtFourAm.InZoneLeniently(eastern);

// Now we can get the difference as an exact elapsed amount of time
Duration difference = zdtFourAmEastern - zdtOneAmEastern;


// Dump the output
Console.WriteLine("ldtOneAm: {0}", ldtOneAm);
Console.WriteLine("ldtFourAm: {0}", ldtFourAm);
Console.WriteLine("period: {0}", period);

Console.WriteLine("zdtOneAmEastern: {0}", zdtOneAmEastern);
Console.WriteLine("zdtFourAmEastern: {0}", zdtFourAmEastern);
Console.WriteLine("difference: {0}", difference);



ldtOneAm: 3/12/2017 1:00:00 AM
ldtFourAm: 3/12/2017 4:00:00 AM
period: PT3H

zdtOneAmEastern: 2017-03-12T01:00:00 America/New_York (-05)
zdtFourAmEastern: 2017-03-12T04:00:00 America/New_York (-04)
difference: 0:02:00:00

我们可以看到三个小时的时间,但这与经过的时间并不完全相同。这仅表示两个局部值在时钟上的位置相隔三个小时。 NodaTime理解这些概念之间的区别,而.Net的内置类型则不然。

We can see a period of three hours, but it doesn't really mean the same as the elapsed time. It just means the two local values are three hours apart in their position on a clock. NodaTime understands the difference between these concepts, while .Net's built-in types do not.

一些后续阅读为您提供帮助:

Some follow-up reading for you:

  • What's wrong with DateTime anyway?
  • More Fun with DateTime
  • The case against DateTime.Now
  • Five Common Daylight Saving Time Antipatterns of .NET Developers

哦,还有另一件事。您的代码有这个...

Oh, and one other thing. Your code has this...

DateTime oneAm = TimeZoneInfo.ConvertTime(new DateTime(2017, 03, 12, 01, 00, 00), easternStandardTime);

由于您创建的 DateTime 具有未指定的种类,您要求将从计算机的本地时区转换为东部时间。如果您在东部时间碰巧不是 ,则您的 oneAm 变量可能根本不是凌晨1点!

Since the DateTime you create has unspecified kind, you are asking to convert from your computer's local time zone to Eastern time. If you happen to be not in Eastern time, your oneAm variable might not be 1 AM at all!

这篇关于为什么不减去两个本地DateTime值来说明夏令时?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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