Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my date time parsing attempt fail?

I'm parsing the same date in two slightly different formats and I encountered an error that I don't understand.

Parsing a string in the standard ISO format is successful:

    String s = "2018-04-17T22:57:29";
    LocalDateTime date = LocalDateTime.parse(s, DateTimeFormatter.ISO_DATE_TIME);  // OK

However when I suffix a "Z" and use ISO_INSTANT thus:

    s = "2018-04-17T22:57:29Z";
    date = LocalDateTime.parse(s, DateTimeFormatter.ISO_INSTANT); // Fails

I get the following exception:

Exception in thread "main" java.time.format.DateTimeParseException: Text '2018-04-17T22:57:29Z' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {MicroOfSecond=0, NanoOfSecond=0, MilliOfSecond=0, InstantSeconds=1524005849},ISO of type java.time.format.Parsed
at java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:1920)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1855)
at java.time.LocalDateTime.parse(LocalDateTime.java:492)
at iplus.fwk.manifest.Test.main(Test.java:37)
Caused by: java.time.DateTimeException: Unable to obtain LocalDateTime from TemporalAccessor: {MicroOfSecond=0, NanoOfSecond=0, MilliOfSecond=0, InstantSeconds=1524005849},ISO of type java.time.format.Parsed
at java.time.LocalDateTime.from(LocalDateTime.java:461)
at java.time.format.Parsed.query(Parsed.java:226)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
... 2 more
Caused by: java.time.DateTimeException: Unable to obtain LocalDate from TemporalAccessor: {MicroOfSecond=0, NanoOfSecond=0, MilliOfSecond=0, InstantSeconds=1524005849},ISO of type java.time.format.Parsed
at java.time.LocalDate.from(LocalDate.java:368)
at java.time.LocalDateTime.from(LocalDateTime.java:456)
... 4 more

My reading of the definition of ISO_INSTANT suggests that the second parse should succeed. What am I doing wrong?

like image 387
dave Avatar asked Mar 05 '23 23:03

dave


1 Answers

I believe that the sound way of thinking of it is this: Conceptually an instant is not a date and time. It’s just that, an instant. It doesn’t have a time zone or offset and therefore cannot have date and time of day. Since a LocalDateTime needs date and time of day, parsing as an instant does not give you what you need.

Yes I know: The implementation of Instant uses time since the epoch, and the by far most common definition of the epoch uses UTC. I also know that the string you were parsing holds a date and a time of day, and that both DateTimeFormatter.ISO_INSTANT and Instant.toString produce similar strings. This is interface, though, and doesn’t tell you what the concept of an instant is.

My reading of the definition of ISO_INSTANT suggests that the second parse should succeed.

I understand that the definition could be read this way. I think that this is worth noting, though:

…the instant is converted from ChronoField.INSTANT_SECONDS and ChronoField.NANO_OF_SECOND…

While nano of second is shared with other date-time types like LocalDateTime, the two fields mentioned are not enough to get you the data for a LocalDateTime. So this is where it breaks for you. To get a LocalDateTime we would need to assume a time zone or offset. Even though the quote goes on:

…using the UTC offset.

— this only means that the UTC offset is used for calculating the instant seconds and nanos, not in the further processing.

We may also try to study your error message a bit closer:

Exception in thread "main" java.time.format.DateTimeParseException: Text '2018-04-17T22:57:29Z' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {MicroOfSecond=0, NanoOfSecond=0, MilliOfSecond=0, InstantSeconds=1524005849},ISO of type java.time.format.Parsed

This also says that all we got from parsing was instant seconds (that is seconds since the epoch) and fractions of seconds (and the ISO chronology). And that this was not enough to obtain a LocalDateTime.

You’re not the first (nor the last) to be surprised. The behaviour you observed is by design, though.

A fix

The definition of ISO_INSTANT also mentions:

The ISO_OFFSET_DATE_TIME

So this works:

    String s = "2018-04-17T22:57:29Z";
    LocalDateTime date = LocalDateTime.parse(s, DateTimeFormatter.ISO_OFFSET_DATE_TIME);

The result is 2018-04-17T22:57:29 (as expected).

like image 156
Ole V.V. Avatar answered Mar 16 '23 20:03

Ole V.V.