[release/11.0] Fix DateTime.ToUniversalTime for invalid local times in the DST gap - #135071
Merged
Merged
Conversation
…134871) Fixes the `DateTime.ToUniversalTime()` regression for `Local` values that fall in the spring-forward DST gap (an invalid time). The invalid-time fallback in `TimeZoneInfo.ConvertTime` added the base UTC offset instead of subtracting it, so an invalid local time was converted to a UTC value two offsets too high (for example `W. Europe` `02:30` became `03:30Z` instead of `01:30Z`). The result now subtracts the offset and agrees with `TimeZoneInfo.Local.GetUtcOffset` and `new DateTimeOffset(local).UtcDateTime`. Added a regression test that sets the local time zone and verifies the invalid-time conversion for several European zones. Fixes #134846 --------- Co-authored-by: Tarek Mahmoud Sayed <tarekms@ntdev.microsoft.com>
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
|
Tagging subscribers to this area: @dotnet/area-system-datetime |
Member
|
Approved offline by email. |
jozkee
approved these changes
Oct 2, 2026
Member
|
/ba-g failures are unrelated |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Backport of #134871 to release/11.0
/cc @tarekgh
Customer Impact
#134846
In .NET 11, converting a local
DateTimein the daylight-saving spring-forward gap to UTC returns the wrong value -ToUniversalTime(),DateTimeOffset, andGetUtcOffset()add the offset instead of subtracting it, landing off by roughly twice the UTC offset. It's a silent regression from .NET 10 (no exception, just wrong data), which can misorder events or corrupt timestamps in scheduling, logging, billing, and calendar scenarios.Regression
The regression came from the
TimeZoneInforewrite in .NET 11 #119662 . That change reworked how invalid (DST-gap) local times are converted, and in the rewritten invalid-time fallback the standard UTC offset was added instead of subtracted - a sign error. Before the rewrite the offset was subtracted correctly; afterward, any local time in the spring-forward gap converts to the wrong UTC instant (off by roughly twice the offset). The .NET 10 code path did not have this error, which is why it's a 10→11 regression.Testing
Verified the failing scenario, added a new test to cover the failing scenario, passing all regression tests.
Risk
Low
This change touches an extremely narrow scenario: only local times that fall inside a daylight-saving spring-forward gap - times the wall clock skips and that rarely occur in practice. Every valid time, ambiguous time, and modern time zone is completely unaffected; the result changes solely for cases that were already returning wrong answers. It reuses existing, vetted rule-comparison logic rather than new math and leaves all hot paths untouched. The worst case is no change, and the expected case is a correct result replacing silent corruption - so accepting it is low risk.