Description
Background and motivation
Very commonly, a DateTime
needs to be converted to a DateTimeOffset
with regard to a specific time zone. There is no built-in mechanism for doing so, and an implementation that handles all edge cases can be error prone.
Indeed, our own docs for how to resolve ambiguous DateTime
values has an error in that the example code doesn't account for BaseUtcOffsetDelta
. I've reported that in dotnet/docs#32773. We also don't provide a single example for resolving both ambiguous and invalid values.
In general we don't make it easy to convert a DateTime
to a DateTimeOffset
for any other time zones than local or UTC.
For context, consider a form where the date and time of an event are collected. Depending on design requirements, the associated time zone might also be collected on the form, or it might be derived from the location of the event, or inferred from the user creating it. The best practice in this scenario is not to pre-convert everything to UTC - but rather to store the DateTime
and time zone ID, then compute a DateTimeOffset
or UTC equivalent when needed.
Also, I've used an extension method for this in at least 5 separate StackOverflow answers for different scenarios.
- Getting a
DateTimeOffset
for today at a given time in a particular time zone - Resolving ambiguous
DateTime
values - Calculating durations correctly with
DateTime
values accounting for DST - Adding a day to a
DateTime
in a specific time zone - Scheduling a job to run in a given time zone accounting for DST
Each of those has additional scenario-specific code, but they all use the same ToDateTimeOffset
extension method that accounts for invalid and ambiguous times, with regard to a specific time zone, and respects DateTimeKind
properly as well. I believe it should be baked in, and perhaps my implementation is suitable (or a good start).
Also note that in all these scenarios, the preference is to use the daylight time for both ambiguous and invalid resolution. The reason for this is that the daylight instance comes first sequentially. This could be optional, but should be the default - as it is well established in scheduling systems where such operations are prevalent.
API Proposal
namespace System;
public struct DateTime
{
public DateTimeOffset ToDateTimeOffset(TimeZoneInfo timeZone);
}
API Usage
using System.Globalization;
// New York, USA (Eastern Time). EST = UTC-05:00, EDT = UTC-04:00
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
// Simple case (00:00 ET becomes 00:00 EST)
DateTime dt1 = DateTime.ParseExact("2022-12-31T00:00:00", "s", CultureInfo.InvariantCulture);
DateTimeOffset dto1 = dt1.ToDateTimeOffset(tz);
Console.WriteLine(dto1.ToString("o", CultureInfo.InvariantCulture)); // 2022-12-31T00:00:00.0000000-05:00
// Example that resolves ambiguous time (1:30 ET becomes 1:30 EDT)
DateTime dt2 = DateTime.ParseExact("2022-11-06T01:30:00", "s", CultureInfo.InvariantCulture);
DateTimeOffset dto2 = dt2.ToDateTimeOffset(tz);
Console.WriteLine(dto2.ToString("o", CultureInfo.InvariantCulture)); // 2022-11-06T01:30:00.0000000-04:00
// Example that resolves invalid time (2:30 ET becomes 3:30 EDT)
DateTime dt3 = DateTime.ParseExact("2022-03-13T02:30:00", "s", CultureInfo.InvariantCulture);
DateTimeOffset dto3 = dt3.ToDateTimeOffset(tz);
Console.WriteLine(dto3.ToString("o", CultureInfo.InvariantCulture)); // 2022-03-13T03:30:00.0000000-04:00
Alternative Designs
This is more verbose, but would do the same and keep it off the DateTime
object.
namespace System;
public class TimeZoneInfo
{
public static DateTimeOffset ConvertDateTimeToDateTimeOffset(DateTime dateTime, TimeZoneInfo timeZone);
}
Or it could be a static method on DateTimeOffset
:
namespace System;
public struct DateTimeOffset
{
public static DateTimeOffset FromDateTime(DateTime dateTime, TimeZoneInfo timeZone);
}
Risks
No response