DateTimeOffset Bug in .Net - Rechenfehler bei Zeitumstellung

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 3 Antworten in diesem Thema. Der letzte Beitrag () ist von petaod.

    DateTimeOffset Bug in .Net - Rechenfehler bei Zeitumstellung

    Die Zeitumstellung am Wochenende hat mir einigen Ärger bereitet.
    Ich testete ob bei diversen Projekte der DST-Switch korrekt implementiert ist.
    Neben ein paar Unzulänglichkeiten in meinem eigenen Code fand ich einen Bug im .Net-Framework.

    Um den Bug zu verifizieren, schrieb ich ein kleines Testprojekt.
    Ich addiere / subtrahiere jeweils eine Viertelstunde auf die relevanten DST-Stunden.
    Ein paar Zeitstempel lieferten dabei fehlerhafte Berechnung.

    Als Workaround ging ich den Umweg über UTC und wandelte wieder in Lokalzeit zurück.
    Da war alles korrekt.

    Bevor ich ein Fass aufmache: Könnte jemand von euch verifizieren, ob das ein generelles Problem ist oder ob es sich nur in meiner Umgebung so verhält.

    VB.NET-Quellcode

    1. Module DstTest
    2. Sub Main()
    3. Dim t1 = New DateTimeOffset(2019, 10, 27, 1, 45, 0, New TimeSpan(2, 0, 0)) '{27.10.2019 01:45:00 +02:00} (01:45)
    4. Dim t2 = New DateTimeOffset(2019, 10, 27, 2, 45, 0, New TimeSpan(2, 0, 0)) '{27.10.2019 02:45:00 +02:00} (02:45A)
    5. Dim t3 = New DateTimeOffset(2019, 10, 27, 2, 45, 0, New TimeSpan(1, 0, 0)) '{27.10.2019 02:45:00 +01:00} (02:45B)
    6. Dim t4 = New DateTimeOffset(2019, 10, 27, 2, 0, 0, New TimeSpan(2, 0, 0)) '{27.10.2019 02:00:00 +02:00} (02:00A)
    7. Dim t5 = New DateTimeOffset(2019, 10, 27, 2, 0, 0, New TimeSpan(1, 0, 0)) '{27.10.2019 02:00:00 +01:00} (02:00B)
    8. Dim t6 = New DateTimeOffset(2019, 10, 27, 3, 0, 0, New TimeSpan(1, 0, 0)) '{27.10.2019 03:00:00 +01:00} (03:00)
    9. ''Dim q = New TimeSpan(0, 15, 0) '{00:15:00}
    10. Console.WriteLine("ADD")
    11. For Each t In {t1, t2, t3}
    12. ''Console.WriteLine($"add Timespan: t=[{t}] q=[{q}] t+q=[{t + q}]")
    13. Console.WriteLine($"AddMinutes : t=[{t}] t.AddMinutes(15)=[{t.AddMinutes(15)}]")
    14. Console.WriteLine($"add via UTC : t=[{t}] t.ToUniversalTime.AddMinutes(15).ToLocalTime=[{t.ToUniversalTime.AddMinutes(15).ToLocalTime}]")
    15. Console.WriteLine()
    16. Next
    17. Console.WriteLine("SUBTRACT")
    18. For Each t In {t4, t5, t6}
    19. ''Console.WriteLine($"subtract Timespan: t=[{t}] q=[{q}] t-q=[{t - q}]")
    20. Console.WriteLine($"AddMinutes : t=[{t}] t.AddMinutes(-15)=[{t.AddMinutes(-15)}]")
    21. Console.WriteLine($"add via UTC : t=[{t}] t.ToUniversalTime.AddMinutes(-15).ToLocalTime=[{t.ToUniversalTime.AddMinutes(-15).ToLocalTime}]")
    22. Console.WriteLine()
    23. Next
    24. End Sub
    25. 'Output (.Net Frameworks 4.7.2, 4.6.1, 4.0, 3.0, 2.0) LocalTimeZone=Europe/Berlin (CET/CEST)
    26. 'ADD
    27. 'AddMinutes : t=[27.10.2019 01:45:00 +02:00] t.AddMinutes(15)=[27.10.2019 02:00:00 +02:00] '(02:00A)
    28. 'add via UTC : t=[27.10.2019 01:45:00 +02:00] t.ToUniversalTime.AddMinutes(15).ToLocalTime=[27.10.2019 02:00:00 +02:00] '(02:00A)
    29. 'AddMinutes : t=[27.10.2019 02:45:00 +02:00] t.AddMinutes(15)=[27.10.2019 03:00:00 +02:00] 'WRONG VALUE (invalid CET/CEST timestamp)
    30. 'add via UTC : t=[27.10.2019 02:45:00 +02:00] t.ToUniversalTime.AddMinutes(15).ToLocalTime=[27.10.2019 02:00:00 +01:00] '(02:00B)
    31. 'AddMinutes : t=[27.10.2019 02:45:00 +01:00] t.AddMinutes(15)=[27.10.2019 03:00:00 +01:00] '(03:00)
    32. 'add via UTC : t=[27.10.2019 02:45:00 +01:00] t.ToUniversalTime.AddMinutes(15).ToLocalTime=[27.10.2019 03:00:00 +01:00] '(03:00)
    33. 'SUBTRACT
    34. 'AddMinutes : t=[27.10.2019 02:00:00 +02:00] t.AddMinutes(-15)=[27.10.2019 01:45:00 +02:00] '(01:45)
    35. 'add via UTC : t=[27.10.2019 02:00:00 +02:00] t.ToUniversalTime.AddMinutes(-15).ToLocalTime=[27.10.2019 01:45:00 +02:00] '(01:45)
    36. 'AddMinutes : t=[27.10.2019 02:00:00 +01:00] t.AddMinutes(-15)=[27.10.2019 01:45:00 +01:00] 'WRONG VALUE (invalid CET/CEST timestamp)
    37. 'add via UTC : t=[27.10.2019 02:00:00 +01:00] t.ToUniversalTime.AddMinutes(-15).ToLocalTime=[27.10.2019 02:45:00 +02:00] '(02:45A)
    38. 'AddMinutes : t=[27.10.2019 03:00:00 +01:00] t.AddMinutes(-15)=[27.10.2019 02:45:00 +01:00] '(02:45B)
    39. 'add via UTC : t=[27.10.2019 03:00:00 +01:00] t.ToUniversalTime.AddMinutes(-15).ToLocalTime=[27.10.2019 02:45:00 +01:00] '(02:45B)
    40. End Module​
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --
    DateTimeOffset Struct
    Obwohl ein DateTimeOffset -Wert einen Offset enthält, handelt es sich dabei nicht um eine voll Zeit Zonen abhängige Datenstruktur. Während ein Offset von UTC ein Merkmal einer Zeitzone ist, wird eine Zeitzone nicht eindeutig identifiziert. Nicht nur mehrere Zeitzonen verwenden denselben Offset von der UTC, sondern der Offset einer einzelnen Zeitzone ändert sich, wenn die Sommerzeit beachtet wird. Dies bedeutet, dass, sobald ein DateTimeOffset Wert von seiner Zeitzone getrennt wird, er nicht mehr eindeutig mit der ursprünglichen Zeitzone verknüpft werden kann.

    d.H. das du beim ersten 3:00Uhr das Offset von UTC+2 (MESZ) auf UTC+1 (MEZ) ändern musst.
    Also ist das kein Bug, sondern ein Feature.
    @petaod

    Ich fühle mit dir

    Freundliche Grüsse

    exc-jdbi

    VB.NET-Quellcode

    1. 'Dieses Jahr beginnen Sommerzeit/Winterzeit um
    2. Dim tz = TimeZone.CurrentTimeZone
    3. Dim gdcstart, gdcend As DateTime
    4. gdcstart = tz.GetDaylightChanges(Year(Now)).Start
    5. gdcend = tz.GetDaylightChanges(Year(Now)).End
    6. Console.WriteLine("Die {0} beginnt am {1} um {2} Uhr ",
    7. tz.DaylightName, gdcstart.ToShortDateString,
    8. gdcstart.ToShortTimeString)
    9. Console.WriteLine("Die {0} (Winterzeit) beginnt am {1} um {2} Uhr ",
    10. tz.StandardName, gdcend.ToShortDateString,
    11. gdcend.ToShortTimeString)
    12. Console.WriteLine()
    13. 'Beispiel
    14. Console.WriteLine("Beispiel")
    15. Console.WriteLine("********")
    16. Dim localtime As DateTime
    17. Dim utc As New DateTime(gdcstart.Year, gdcstart.Month, gdcstart.Day, 0, 0, 0)
    18. Console.WriteLine("Es ist UTC: {0}", utc)
    19. While utc.Hour <= 2
    20. localtime = TimeZoneInfo.ConvertTimeFromUtc(utc, TimeZoneInfo.Local)
    21. Console.WriteLine("Zonenzeit {0} DST (Sommerzeit): {1}", localtime,
    22. localtime.IsDaylightSavingTime)
    23. Console.WriteLine("+1 Hour")
    24. utc = utc.AddHours(1)
    25. End While
    26. Console.WriteLine()
    27. utc = New DateTime(gdcend.Year, gdcend.Month, gdcend.Day, 0, 0, 0)
    28. Console.WriteLine("Es ist UTC: {0}", utc)
    29. While utc.Hour <= 2
    30. localtime = TimeZoneInfo.ConvertTimeFromUtc(utc, TimeZoneInfo.Local)
    31. Console.WriteLine("Zonenzeit {0} DST (Winterzeit): {1}",
    32. localtime, Not localtime.IsDaylightSavingTime)
    33. Console.WriteLine("+1 Hour")
    34. utc = utc.AddHours(1)
    35. End While
    36. Console.WriteLine()
    37. Console.ReadLine()

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „exc-jdbi“ ()

    HenryV schrieb:

    Also ist das kein Bug, sondern ein Feature.
    Ein Feature, das Microsoft nicht bis zu Ende gedacht hat.

    Das merkwürdige ist, dass er beim Umweg über UTC den Zeitzonenwechsel erkennt.
    Und laut MSDN-Blog soll für solche Fälle DateTimeOffset ausgelegt sein.
    Use DateTimeOffset to:
    Work with time zones.
    Work with daylight saving times.


    Aber ich habe ein wenig rumgegoogelt. Das Problem ist bekannt.
    Die einzig grundlegend funktionierende Methode ist, die TimeZoneInfo-Klasse zu verwenden (Edit: so wie es @exc-jdbi auch vorschlug).
    Vor allem, weil auch mein Workaround ->ToUniversalTime->Calculate->ToLocalTime nur für die lokale Zeitzone funktioniert und ich gelegentlich auch im selben Programm mit Daten aus drei Zeitzonen arbeite, von denen nur eine "local" ist.

    Genau dieser im MSDN-Blog erwähnte Rat Use DateTimeOffset to ... work with daylight saving times sollte nicht befolgt werden, weil er nur unvollständig implementiert ist.
    Anscheinend ist nicht alles richtig, was im Internet steht. ;)
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --