为什么减去两个本地 DateTime 值似乎不能解释夏令时?
Why doesn't subtracting two local DateTime values appear to account for Daylight Saving Time?
我正在玩一些 C# 代码,以尝试了解 C# 中减去 DateTime 对象如何根据夏令时工作。
根据 Google 和其他来源,2017 年东部标准时区的夏令时 "spring ahead" 事件发生在 3 月 12 日 2:00am。因此,前几个小时当天的人数是:
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:00am 和 4:00am 之间的时差,我希望结果是 2小时。
但是,我放在一起试图模拟这个问题的代码返回了 3 小时的时间跨度。
代码:
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);
在我的电脑上,这会生成:
2017-03-12 01:00:00.000 -5
2017-03-12 04:00:00.000 -4
False
True
03:00:00
所有输出都符合预期——除了 3 小时的最终值,正如我上面提到的,我预计是 2 小时。
显然,我的代码没有正确地模拟我想到的情况。有什么缺陷?
MSDN 文档中解决了这个问题。
基本上,当从一个日期减去另一个日期时,您应该使用 DateTimeOffset.Subtract()
而不是像这里那样使用算术减法。
TimeSpan difference = fourAm.Subtract(oneAm);
产生预期的 2 小时时差。
好的,所以我对您的代码做了一些小改动。不确定这是否是您想要实现的目标,但这会给您想要的...
static void Main() {
TimeZoneInfo easternStandardTime = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
TimeZone timeZone = TimeZone.CurrentTimeZone;
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);
DaylightTime time = timeZone.GetDaylightChanges(fourAm.Year);
TimeSpan difference = ((fourAm - time.Delta) - oneAm);
Console.WriteLine(oneAm);
Console.WriteLine(fourAm);
Console.WriteLine(TimeZoneInfo.Local.IsDaylightSavingTime(oneAm));
Console.WriteLine(TimeZoneInfo.Local.IsDaylightSavingTime(fourAm));
Console.WriteLine(difference);
Console.ReadLine();
}
观察:
// 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
,默认情况下为 Unspecified
。它不属于任何特定时区。 DateTimeOffset
没有类型,它有一个 Offset
,它告诉您本地时间与 UTC 的偏移量。 这些 都不会给您时区。这就是 TimeZoneInfo
对象正在做的事情。请参阅 the timezone tag wiki 中的 "time zone != offset"。
我认为您可能感到沮丧的部分是,由于几个历史原因,DateTime
对象在做数学时 永远 不理解时区,甚至当你可能 DateTimeKind.Local
。它可以实现以观察本地时区的转换,但它不是那样做的。
您可能也对 Noda Time 感兴趣,它以更明智和更有目的性的方式在 .NET 中提供了非常不同的 API 日期和时间。
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 的内置类型则不了解。
一些后续阅读供您阅读:
- What's wrong with DateTime anyway?
- More Fun with DateTime
- The case against DateTime.Now
- Five Common Daylight Saving Time Antipatterns of .NET Developers
哦,还有一件事。你的代码有这个...
DateTime oneAm = TimeZoneInfo.ConvertTime(new DateTime(2017, 03, 12, 01, 00, 00), easternStandardTime);
由于您创建的 DateTime
类型未指定,您要求将 从您计算机的本地时区 转换为东部时间。如果您碰巧 不是 东部时间,您的 oneAm
变量可能根本不是凌晨 1 点!
我正在玩一些 C# 代码,以尝试了解 C# 中减去 DateTime 对象如何根据夏令时工作。
根据 Google 和其他来源,2017 年东部标准时区的夏令时 "spring ahead" 事件发生在 3 月 12 日 2:00am。因此,前几个小时当天的人数是:
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:00am 和 4:00am 之间的时差,我希望结果是 2小时。
但是,我放在一起试图模拟这个问题的代码返回了 3 小时的时间跨度。
代码:
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);
在我的电脑上,这会生成:
2017-03-12 01:00:00.000 -5
2017-03-12 04:00:00.000 -4
False
True
03:00:00
所有输出都符合预期——除了 3 小时的最终值,正如我上面提到的,我预计是 2 小时。
显然,我的代码没有正确地模拟我想到的情况。有什么缺陷?
MSDN 文档中解决了这个问题。
基本上,当从一个日期减去另一个日期时,您应该使用 DateTimeOffset.Subtract()
而不是像这里那样使用算术减法。
TimeSpan difference = fourAm.Subtract(oneAm);
产生预期的 2 小时时差。
好的,所以我对您的代码做了一些小改动。不确定这是否是您想要实现的目标,但这会给您想要的...
static void Main() {
TimeZoneInfo easternStandardTime = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
TimeZone timeZone = TimeZone.CurrentTimeZone;
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);
DaylightTime time = timeZone.GetDaylightChanges(fourAm.Year);
TimeSpan difference = ((fourAm - time.Delta) - oneAm);
Console.WriteLine(oneAm);
Console.WriteLine(fourAm);
Console.WriteLine(TimeZoneInfo.Local.IsDaylightSavingTime(oneAm));
Console.WriteLine(TimeZoneInfo.Local.IsDaylightSavingTime(fourAm));
Console.WriteLine(difference);
Console.ReadLine();
}
观察:
// 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
,默认情况下为 Unspecified
。它不属于任何特定时区。 DateTimeOffset
没有类型,它有一个 Offset
,它告诉您本地时间与 UTC 的偏移量。 这些 都不会给您时区。这就是 TimeZoneInfo
对象正在做的事情。请参阅 the timezone tag wiki 中的 "time zone != offset"。
我认为您可能感到沮丧的部分是,由于几个历史原因,DateTime
对象在做数学时 永远 不理解时区,甚至当你可能 DateTimeKind.Local
。它可以实现以观察本地时区的转换,但它不是那样做的。
您可能也对 Noda Time 感兴趣,它以更明智和更有目的性的方式在 .NET 中提供了非常不同的 API 日期和时间。
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 的内置类型则不了解。
一些后续阅读供您阅读:
- What's wrong with DateTime anyway?
- More Fun with DateTime
- The case against DateTime.Now
- Five Common Daylight Saving Time Antipatterns of .NET Developers
哦,还有一件事。你的代码有这个...
DateTime oneAm = TimeZoneInfo.ConvertTime(new DateTime(2017, 03, 12, 01, 00, 00), easternStandardTime);
由于您创建的 DateTime
类型未指定,您要求将 从您计算机的本地时区 转换为东部时间。如果您碰巧 不是 东部时间,您的 oneAm
变量可能根本不是凌晨 1 点!