.Net DateTime with local time and DST

后端 未结 2 1372
慢半拍i
慢半拍i 2021-01-13 17:48

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 enlig

相关标签:
2条回答
  • 2021-01-13 18:19

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

    0 讨论(0)
  • 2021-01-13 18:23

    Yes, the DateTime type in .NET is major mess, as you can observe because it doesn't support the concepts of time-zones, multiple calendars and many other useful concepts such as intervals etc.

    A little better is the DateTimeOffset type which adds time-zone offset information. The DateTimeOffset will allow you to more accurately represent the times you show in your question and comparisons will take the timezone offset into account. But this type is not perfect either. It still does not support true time zone information, only the offset. So it is not possible to perform complex DST calculations or to support advanced calendars.

    For a much more thorough solution you can use NodaTime

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