Why doesn't subtracting two local DateTime values appear to account for Daylight Saving Time?

久未见 提交于 2019-12-04 12:07:32
Matt Johnson-Pint

Observe:

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

Results:

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

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.

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.

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

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:

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

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

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!

So this is addressed in the MSDN documentation.

Basicaly, when subtracting one date from another you should be using DateTimeOffset.Subtract() and not arithmetic subtraction as you have here.

TimeSpan difference = fourAm.Subtract(oneAm);

Yields the expected 2 hour time difference.

Ok, so I made some minor changes to your code. Not sure if this is what you are trying to achieve or not but this will give you what you want...

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();
    }
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!