Skip to content

Conversation

tarekgh
Copy link
Member

@tarekgh tarekgh commented Sep 12, 2025

This change aims to improve time zone handling in .NET.

Current Issues

  • Performance problems: Multiple reports highlight performance concerns when converting times across different time zones—especially between local and UTC. The performance degradation is even more noticeable on Linux. More details are included below. Examples of reported issues: #24839, #24277, #25075.

  • Correctness and reliability: Users occasionally report incorrect results when working with time zones. A major contributing factor is that the time zone handling code was originally written long ago and has since been patched many times. These incremental fixes have made the implementation difficult to maintain, and it’s often unclear whether a new fix might cause regressions elsewhere. While we have fair to good test coverage, it cannot guarantee correctness across all possible time conversion scenarios and supported time zones. Examples of reported issues: #118915, #114476.

How Time Zones Work Today

When converting time between time zones, .NET primarily relies on the base UTC offset (e.g., -8:00 hours for Pacific Standard Time) along with historical and future transition data for each zone. These transitions are expressed through AdjustmentRules, where each rule covers one or more years.

An AdjustmentRule specifies:

  • The start and end of the years it applies to.
  • When daylight saving time (DST) begins and ends.
  • Any difference in the base UTC offset for a particular year, represented by BaseUtcOffsetDelta.

Data sources

  • Windows: Rules are read from the Registry at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones.
  • Linux: Rules come from the tzdata package, which contains the IANA database. IANA data is much more comprehensive, including detailed historical records for every time zone. This means Linux typically has far more rules per time zone than Windows.

Rule types

  1. Fixed rules – explicitly define the absolute start and end dates/times of DST transitions.
  2. Floating rules – define transitions in relative terms (e.g., “DST starts on the first Sunday in March at 2:00 AM, and ends on the last Sunday in November at 2:00 AM”).
  • On Windows, most rules are stored as floating rules, so a single rule can cover many years.
  • On Linux, most rules are fixed, which leads to a much larger number of entries. Linux also includes some floating rules, typically for future years.

Another difference: Windows stores transition times in local time, while Linux stores them in UTC. Since the sources differ, .NET does not normalize this when reading the data.

Conversion process

When converting time between zones, .NET:

  1. Checks whether the zone supports daylight saving time.

  2. Locates the appropriate rule for the target year.

    • For floating rules, the exact transition dates are calculated (e.g., Pacific Standard Time in 2025 transitions on March 9 at 2:00 AM and November 2 at 2:00 AM).
  3. Applies the rule to perform the conversion.

    • If the conversion crosses year boundaries, rules from adjacent years may also need to be consulted.
    • Special cases exist for southern hemisphere zones, where DST may be active at the start and end of the year, with standard time only in the middle. In these cases, a single year can include multiple transitions.

Summary

The conversion process involves significant computation, especially on Linux, where the number of rules per time zone is much higher.

The Change

We are introducing a per-year cache to store time zone transitions. This cache is created on-demand—only when a given year is accessed—and contains all possible transitions for that year.

Each cache entry includes:

  • A list of all transitions within the year.
  • The corresponding time offsets for each transition.
  • A flag indicating whether the period is daylight saving or standard time.
  • All date/times stored in UTC, ensuring consistency during conversions.

With this approach, time conversion requires only a single cache lookup, without repeatedly consulting the underlying rules. The cache mapping is stored in a concurrent dictionary, allowing efficient thread-safe access.

Benefits

  • Performance: Eliminates repeated rule lookups by reusing cached transitions.
  • Simplicity: Conversion logic now uses helper methods backed by the cache, replacing the more complex rule-based path.
  • Compatibility: Care was taken to maintain application compatibility, particularly for cases where conversion behavior depends on DateTime.Kind.

In other words, rules are used only when building a new cache entry; once cached, all conversions run through the simplified lookup path.

Note: In typical scenarios, users work with only a small range of years for time conversions, so the cache should not pose any issues. For extreme cases, developers can clear the cache using TimeZoneInfo.ClearCachedData.

Testing

  • Performed extensive manual testing on both Windows and Linux, validating nearly every second across all supported years and time zones.
  • Verified that all regression tests pass successfully on Windows and Linux, ensuring compatibility.
  • Added new test cases to cover previously reported issues that are now resolved with this change.
  • Conducted performance measurements for various time zone operations, including DateTime.Now.

Performance results

Windows

BenchmarkDotNet v0.15.2, Windows 11 (10.0.26100.6584/24H2/2024Update/HudsonValley)
11th Gen Intel Core i7-11700 2.50GHz, 1 CPU, 16 logical and 8 physical cores
Faster base/diff Base Median (ns) Diff Median (ns)
Baseline.Program.ConvertTimeFromUtc 3.93 47.97 12.21
Baseline.Program.ConvertTimeUsingLocalDateKind 3.47 66.70 19.21
Baseline.Program.ConvertTimeUsingUnspecifiedDateKind 3.34 63.77 19.07
Baseline.Program.ConvertTimeToUtc 2.98 48.33 16.23
Baseline.Program.DateTimeNow 2.81 54.57 19.42
Baseline.Program.GetUtcOffset 2.48 98.64 39.83
Baseline.Program.ConvertTimeUsingUtcDateKind 2.39 19.66 8.23

Linux (WSL)

BenchmarkDotNet v0.15.2, Linux Ubuntu 25.04 (Plucky Puffin)
11th Gen Intel Core i7-11700 2.50GHz, 1 CPU, 16 logical and 8 physical cores
Faster base/diff Base Median (ns) Diff Median (ns)
Baseline.Program.ConvertTimeToUtc 4.70 63.80 13.57
Baseline.Program.ConvertTimeUsingLocalDateKind 4.25 80.09 18.84
Baseline.Program.ConvertTimeUsingUnspecifiedDateKind 4.01 79.23 19.78
Baseline.Program.ConvertTimeFromUtc 3.54 39.34 11.12
Baseline.Program.DateTimeNow 3.51 50.99 14.54
Baseline.Program.GetUtcOffset 2.87 100.40 34.94
Baseline.Program.ConvertTimeUsingUtcDateKind 1.64 17.60 10.73

Opportunities

This change opens the door to several improvements:

  • API simplification: We could refine our APIs to offer a more streamlined experience. While we already recommend using DateTimeOffset instead of DateTime for time zone scenarios, many developers still rely on DateTime and often run into issues related to DateTime.Kind. A cleaner API surface could help mitigate these problems.
  • Additional data sources: With the new caching approach, we could more easily support alternative time zone data sources, such as ICU, to improve coverage on Windows. The main challenge would be exposing rules through existing APIs, though these rules would no longer be required for computation.

Fixes #24839, #24277, #25075, #118915, #114476

@Copilot Copilot AI review requested due to automatic review settings September 12, 2025 19:01
@tarekgh tarekgh added this to the 11.0.0 milestone Sep 12, 2025
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements a comprehensive performance improvement to .NET's time zone handling system by introducing a per-year caching mechanism for time zone transitions. The change aims to address significant performance issues, particularly on Linux, when converting times across different time zones and during DateTime.Now operations.

Key changes:

  • Introduces per-year caching of time zone transitions to eliminate repeated rule lookups
  • Replaces complex rule-based conversion logic with simplified cache-backed helper methods
  • Streamlines DateTime.Now implementation using optimized caching for better performance

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
System/TimeZoneInfo.Cache.cs New file implementing the core caching mechanism for time zone transitions
System/TimeZoneInfo.cs Major refactoring to use cache-based conversion methods instead of rule-based logic
System/TimeZoneInfo.Win32.cs Cleanup of Windows-specific DateTime.Now optimization code
System/TimeZoneInfo.Unix.cs Cleanup of Unix-specific DateTime.Now optimization code
System/DateTime.cs Simplified DateTime.Now implementation using new caching system
System.Private.CoreLib.Shared.projitems Added new cache file to build system
System/TimeZoneInfoTests.cs Added comprehensive test cases for invalid and ambiguous time detection

Copy link
Member

@jozkee jozkee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionally, looks got to me; it is quite complex though, left a few comments to improve readability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DateTime.Now takes 1 second to execute on WSL

4 participants