Skip to content

[API Proposal]: DateTime.ToDateTimeOffset(TimeZoneInfo) #78934

Open
@mattjohnsonpint

Description

@mattjohnsonpint

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.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions