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

前端 未结 3 1182
感情败类
感情败类 2021-01-06 05:12

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.

Per Google and o

相关标签:
3条回答
  • 2021-01-06 05:59

    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();
        }
    
    0 讨论(0)
  • 2021-01-06 05:59

    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.

    0 讨论(0)
  • 2021-01-06 06:06

    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:

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

    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!

    0 讨论(0)
提交回复
热议问题