dotnet / dotnet/docs

Guidance for resolving ambiguous DateTimes does not account for BaseUtcOffsetDelta

Open
#32,773 10 comments 0 reactions 0 assignees View on GitHub
dotnet-fundamentals/svc
Dominant language
No language data
Stars
4.8k
Forks
6.1k
Avg merge
15h 21m
Merged PRs (30d)
370

Description

In several places in the guidance for resolving ambiguous `DateTime` values (both in [this doc](https://learn.microsoft.com/dotnet/standard/datetime/resolve-ambiguous-times) and [this doc](https://learn.microsoft.com/dotnet/standard/datetime/let-users-resolve-ambiguous-times)), the example code uses the `BaseUtcOffset` property of the time zone to determine the offset for standard time. This unfortunately is a bug, because it does not take into account any [`BaseUtcOffsetDelta`](https://learn.microsoft.com/dotnet/api/system.timezoneinfo.adjustmentrule.baseutcoffsetdelta) that may apply.

More about `BaseUtcOffsetDelta` in the blog post here:
https://devblogs.microsoft.com/dotnet/date-time-and-time-zone-enhancements-in-net-6/#timezoneinfo-adjustmentrule-improvements

Both `GetUtcOffset` and `GetAmbiguousTimeOffsets` will take `BaseUtcOffset` and `BaseUtcOffsetDelta` into account - the problem is only when using `BaseUtcOffset` by itself, because that assumes the `BaseUtcOffsetDelta` is zero.

Also - A much better way to resolve ambiguous and invalid `DateTime` values is to account for both with respect to a given time zone and resolve to a `DateTimeOffset`. I've used this approach for some time now with the following extension method on several StackOverflow answers (such as [this one](https://stackoverflow.com/a/74306764/634824)) and in real apps. Feel free to use it in revised guidance:

```csharp
public static DateTimeOffset ToDateTimeOffset(this DateTime dt, TimeZoneInfo tz)
{
if (dt.Kind != DateTimeKind.Unspecified)
{
// Handle UTC or Local kinds (regular and hidden 4th kind)
DateTimeOffset dto = new DateTimeOffset(dt.ToUniversalTime(), TimeSpan.Zero);
return TimeZoneInfo.ConvertTime(dto, tz);
}

if (tz.IsAmbiguousTime(dt))
{
// Prefer the daylight offset, because it comes first sequentially (1:30 ET becomes 1:30 EDT)
TimeSpan[] offsets = tz.GetAmbiguousTimeOffsets(dt);
TimeSpan offset = offsets[0] > offsets[1] ? offsets[0] : offsets[1];
return new DateTimeOffset(dt, offset);
}

if (tz.IsInvalidTime(dt))
{
// Advance by the gap, and return with the daylight offset (2:30 ET becomes 3:30 EDT)
TimeSpan[] offsets = { tz.GetUtcOffset(dt.AddDays(-1)), tz.GetUtcOffset(dt.AddDays(1)) };
TimeSpan gap = offsets[1] - offsets[0];
return new DateTimeOffset(dt.Add(gap), offsets[1]);
}

// Simple case
return new DateTimeOffset(dt, tz.GetUtcOffset(dt));
}
```

---
#### Document Details

⚠ *Do not edit this section. It is required for learn.microsoft.com ➟ GitHub issue linking.*

* ID: 0b8b01b8-3227-55ec-1ec4-5c6a6a9b5603
* Version Independent ID: 1547c1eb-3c9f-fc0a-7e00-3163bfdb1438
* Content: [How to: Let users resolve ambiguous times](https://learn.microsoft.com/en-us/dotnet/standard/datetime/let-users-resolve-ambiguous-times)
* Content Source: [docs/standard/datetime/let-users-resolve-ambiguous-times.md](https://github.com/dotnet/docs/blob/main/docs/standard/datetime/let-users-resolve-ambiguous-times.md)
* Product: **dotnet-fundamentals**
* GitHub Login: @adegeo
* Microsoft Alias: **adegeo**

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.