Skip to content

DateTime UTC conversion to local time does not use historical DST rules #120434

Closed as duplicate

Description

@lscorcia

Description

Hi, we noticed an unexpected behavior when working with UTC DateTimes.
Some context: a web app sends UTC dates in ISO8601 format via JSON to a .net backend. On the server side they are deserialized to DateTime. We then use DateTime.ToLocalTime in order to render a PDF containing the user-entered DateTime.

Crucially, both client and server are located in Italy, "Europe/Rome" time zone. Here the current DST rules today say that DST ends "the last Sunday of October". This rule is active since 1994; before that it ended "the last Sunday of September". When the client wants to send the date "30/09/1993 00:00:00", it correctly sends it as the ISO8601 value "1993-09-29T23:00:00Z". However, when we print the date in the PDF report we call DateTime.ToLocalTime as we want the same value the user entered to be displayed in the document. Here .net uses the current DST adjustment rule instead of the historical one, and this makes the displayed date into "30/09/1993 01:00:00".

I have verified that .net does not actually have its own table but loads it from the underlying OS. Indeed, the error is only present when running under Windows while running under Linux renders the date correctly. A nice rabbit hole led me to a registry key on Windows where DST rules are stored, and it actually seems that Windows 11 does not include historical DST rules.

It feels really strange that we are the only ones noticing this behavior, but I wasn't able to find any other source dealing with this issue on Google. I understand it's not fixable by the .net team alone, but I'm looking for some guidance on how to resolve this issue. I also tried to use App Local ICU (https://learn.microsoft.com/en-us/dotnet/core/extensions/globalization-icu#app-local-icu) hoping it would load the same rules on Windows as it does on Linux, but it didn't have any effect.

Do you have any suggestion?

Reproduction Steps

Under Windows:

string strUtcA = "1993-09-19T22:00:00Z";
string strUtcB = "1993-09-29T23:00:00Z";

DateTime dtA = DateTime.Parse(strUtcA).ToUniversalTime();
DateTime dtB = DateTime.Parse(strUtcB).ToUniversalTime();

Console.WriteLine(strUtcA + " > UTC " + dtA.ToString("o") + " > Local " + dtA.ToLocalTime());
Console.WriteLine(strUtcB + " > UTC " + dtB.ToString("o") + " > Local " + dtB.ToLocalTime());

Output (wrong):

1993-09-19T22:00:00Z > UTC 1993-09-19T22:00:00.0000000Z > Local 20/09/1993 00:00:00
1993-09-29T23:00:00Z > UTC 1993-09-29T23:00:00.0000000Z > Local 30/09/1993 01:00:00

Under Linux

string strUtcA = "1993-09-19T22:00:00Z";
string strUtcB = "1993-09-29T23:00:00Z";

DateTime dtA = DateTime.Parse(strUtcA).ToUniversalTime();
DateTime dtB = DateTime.Parse(strUtcB).ToUniversalTime();

Console.WriteLine(strUtcA + " > UTC " + dtA.ToString("o") + " > Local " + dtA.ToLocalTime());
Console.WriteLine(strUtcB + " > UTC " + dtB.ToString("o") + " > Local " + dtB.ToLocalTime());

Output (correct):

1993-09-19T22:00:00Z > UTC 1993-09-19T22:00:00.0000000Z > Local 20/09/1993 00:00:00
1993-09-29T23:00:00Z > UTC 1993-09-29T23:00:00.0000000Z > Local 30/09/1993 00:00:00

Expected behavior

The output should be the same between Windows and Linux. The Linux one is correct - in 1993 a different DST rule was in place.

Actual behavior

When running on Windows the current DST rule is applied, leading to spurious +/- 1 hour differences.

Regression?

Not a regression AFAIK.

Known Workarounds

NodaTime on Windows behaves correctly as it doesn't use OS tables for DST changes. However switching DateTime for NodaTime is not a straightforward replacement.

Configuration

Tested with .net 6 and 9, on Linux (Ubuntu 22) and Windows.

Other information

No response

Activity

  1. dotnet-policy-service commented on Oct 6, 2025

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @dotnet/area-system-datetime
    See info in area-owners.md if you want to be subscribed.

  2. MihuBot commented on Oct 6, 2025

    @MihuBot

    I'm a bot. Here are possible related and/or duplicate issues (I may be wrong):

  3. locked and limited conversation to collaborators on Nov 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions