How should I convert a local DateTime to an Instant?

Sam picture Sam · Oct 17, 2013 · Viewed 8.1k times · Source

I have a situation where I would like to convert a DateTime to an Instant. I believe the DateTime's Kind will be Local.

Given that the DateTime is in a variable called time, the closest I could find in the library is:

Instant.FromDateTimeUtc(time.ToUniversalTime())

This seems reasonable, but I want to be certain that this is completely reliable and doesn't have the risk of data loss or corruption. I'm not certain if this is the best way to do the conversion or if there's a more reliable means to do so.

I looked at NodaTime's BCL Conversions page, and it said the following about this scenario:

Note that there are no conversions to a DateTime with a kind of Local - this would effectively be for the system default time zone, which you should generally be explicit about to start with.

Answer

Matt Johnson-Pint picture Matt Johnson-Pint · Oct 19, 2013

You're missing one critical point: A DateTime whose kind is Local doesn't always fully represent a unique moment in time. That is why there is no direct mapping to an Instant.

During a fall-back DST transition, a local DateTime can represent either of two possible moments in time. If you're going to convert it to an Instant, then somewhere you need to decide which moment in time you should choose.

In the answer you gave, I'm assuming you obtained the timezone from one of the following:

var timezone = DateTimeZoneProviders.Tzdb.GetSystemDefault();

or

var timezone = DateTimeZoneProviders.Bcl.GetSystemDefault();

Either are ok for this task. Then the code you gave:

var localTime = LocalDateTime.FromDateTime(time);
var zonedTime = localTime.InZoneStrictly(timeZone);
return zonedTime.ToInstant();

This is exactly correct, but since you used InZoneStrictly, you will get an AmbiguousTimeException during the fall-back transition.

You can avoid this by using InZoneLeniently, which will pick the latter of the two possibilities (usually the "Standard" time). But more importantly, you can instead use InZone and provide either a standard or custom resolver to control the behavior more precisely.

Regarding your original approach of:

Instant.FromDateTimeUtc(time.ToUniversalTime())

This is ok and will not corrupt your data, but understand that it will rely on the BCL's behavior of local to universal conversion. It is identical to InZoneLeniently, in that an ambiguous value will be treated as the "standard" time.

This is a great example of how NodaTime offers an API that is more precise. Instead of making assumptions, you have the opportunity to be specific and provide custom behavior. In the end you achieved the same result, but it brought this issue to the foreground instead of hiding it.