Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parsing ambiguous datetime with Noda Time

Tags:

c#

.net

nodatime

I use Noda Time, and have the following code:

var pattern = ZonedDateTimePattern.CreateWithInvariantCulture(
    "yyyy-MM-dd HH:mm:ss z", 
    DateTimeZoneProviders.Tzdb);

var parsed = pattern.Parse("2017-11-05 01:00:00 America/Los_Angeles");
Console.WriteLine(parsed.Value);

This results in an UnparsableValueException with the message:

The local date/time is ambiguous in the target time zone

The problem, as I understand it, is that this specific time can occur twice because of daylight saving. At 02:00, the clock is turned back one hour to 01:00. NodaTime doesn't know which "version" of 01:00 that the string refers to, and because of that the exception is thrown.

To me, it doesn't really matter which version of the time that the parse results in, I just want to avoid the exception, and get a date that is as close to reality as possible. One hour less or more is OK. What would be the best way to do that?

The only way I can think of is splitting the string and parsing the parts individually, and then add one hour, but that feels completely wrong. Is there a better solution?

like image 730
TheQ Avatar asked Feb 14 '18 10:02

TheQ


2 Answers

The ZonedDateTimePattern class has a Resolver property. The resolver's role is to perform the mapping to zoned date/times and handle skipped and ambiguous times - times which cannot be mapped as they will occur never (skipped) or more than once (ambiguous) due to DST.

The ZonedDateTimePattern source code shows that the default resolver is Resolvers.StrictResolver. As you have already discovered, this resolver throws an exception if the mapping is ambiguous or skipped.

A variety of resolvers are available. The best match for your "just give me a valid date and time please!" requirement is likely to be the LenientResolver which behaves as follows:

Ambiguity is handled by returning the earlier occurrence, and skipped times are shifted forward by the duration of the gap.

We can specify this resolver by appending a call to WithResolver() on our ZonedDateTimePattern instance (the Resolver property does not have a public setter):

var pattern = ZonedDateTimePattern.CreateWithInvariantCulture(
    "yyyy-MM-dd HH:mm:ss z",
    DateTimeZoneProviders.Tzdb).WithResolver(Resolvers.LenientResolver);

var parsed = pattern.Parse("2017-11-05 01:00:00 America/Los_Angeles");
Console.WriteLine(parsed.Value);

Output:

2017-11-05T01:00:00 America/Los_Angeles (-07)

like image 101
Stephen Kennedy Avatar answered Oct 24 '22 03:10

Stephen Kennedy


Per https://github.com/nodatime/nodatime/blob/2.2.x/src/NodaTime.Web/Markdown/2.0.x/zoneddatetime-patterns.md (or wherever that is generated to)

If the pattern does not contain an offset specifier ("o<...>") the local date and time represented by the text is interpreted according to the ZoneLocalMappingResolver associated with the pattern. A new pattern can be created from an existing one, just with a different resolver, using the WithResolver method. If the resolver throws a SkippedTimeException or AmbiguousTimeException, these are converted into UnparsableValueException results. Note that a pattern without an offset specifier will always lead to potential data loss when used with time zones which aren't a single fixed offset, due to the normal issues of time zone transitions (typically for daylight saving time).

suggesting

var lenientpattern = ZonedDateTimePattern
                    .CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss z", DateTimeZoneProviders.Tzdb)
                    .WithResolver(Resolvers.LenientResolver); //or any of the other resolvers
var parsed = lenientpattern.Parse("2017-11-05 01:00:00 America/Los_Angeles");
Console.WriteLine(parsed.Value);
like image 3
Martijn Avatar answered Oct 24 '22 03:10

Martijn