Open mattjohnsonpint opened 2 years ago
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.
@tarekgh
For consideration, here is the extension method I've been using and suggesting to others. The actual implementation could deviate from this, but would need to handle the same cases outlined.
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));
}
Alternative name: WithOffsetForTimeZone
Another example use case: https://stackoverflow.com/a/77294370/634824
@tarekgh @danmoseley - Can we get some eyes on this for .NET 9? It's very much needed, and there is demand for it, as evidenced by the StackOverflow links above. Thanks.
I also just added another alternative API to the top, as it could be a factory method on DateTimeOffset
.
I was also thinking, with any of these, it might be worth having two methods - one that takes a TimeZoneInfo
for the scenarios proposed, and one that takes a TimeSpan
for fixed offsets. In other words:
namespace System;
public struct DateTimeOffset
{
public static DateTimeOffset FromDateTime(DateTime dateTime, TimeZoneInfo timeZone);
public static DateTimeOffset FromDateTime(DateTime dateTime, TimeSpan offset);
}
.... we really need to get our own DateTimeZoned
type....
public static DateTimeOffset FromDateTime(DateTime dateTime, TimeSpan offset);
This will look very weird because DateTimeOffset already have a constructor which takes DateTime and TimeSpan offset.
I am leaning to the original proposal but I want to give more control for the user on the behavior. What do you think about:
// Method defined in DateTime type.
public static DateTimeOffset ToDateTimeOffset(TimeZoneInfo tz = TimeZoneInfo.Local, bool useDaylightOffsetWhenAmbiguous = true, bool throwWhenInvalid = false)
Background and motivation
Very commonly, a
DateTime
needs to be converted to aDateTimeOffset
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 forBaseUtcOffsetDelta
. I've reported that in https://github.com/dotnet/docs/issues/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 aDateTimeOffset
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 aDateTimeOffset
or UTC equivalent when needed.Also, I've used an extension method for this in at least 5 separate StackOverflow answers for different scenarios.
DateTimeOffset
for today at a given time in a particular time zoneDateTime
valuesDateTime
values accounting for DSTDateTime
in a specific time zoneEach 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 respectsDateTimeKind
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
API Usage
Alternative Designs
This is more verbose, but would do the same and keep it off the
DateTime
object.Or it could be a static method on
DateTimeOffset
:Risks
No response