Freak
Freak

Reputation: 6873

unable to parse String-Date to java.time.LocalDateTime with specific format

Following code prints output with Date in String with timeZone offset.

String BERLIN_TIME_ZONE = "Europe/Berlin";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'hh:mm:ssZ");
String dateTimeInString = formatter
        .format(ZonedDateTime.of(LocalDateTime.now(), ZoneId.of(BERLIN_TIME_ZONE)));

System.out.println(dateTimeInString); // 2021-06-07T02:12:08+0200

This works perfectly fine. Now, at this point when we have dateTimeInString so I want to convert it back to LocalDateTime object by using parse method.

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'hh:mm:ssZ");
System.out.println(LocalDateTime.parse(dateTimeInString,formatter));

but this parsing is throwing an exception.

Exception in thread "main" java.time.format.DateTimeParseException: Text '2021-06-07T02:12:08+0200' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {SecondOfMinute=8, MinuteOfHour=12, HourOfAmPm=2, OffsetSeconds=7200, NanoOfSecond=0, MilliOfSecond=0, MicroOfSecond=0},ISO resolved to 2021-06-07 of type java.time.format.Parsed
    at java.base/java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:2017)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1952)
    at java.base/java.time.LocalDateTime.parse(LocalDateTime.java:492)
    at de.finhome.A.main(A.java:19)
Caused by: java.time.DateTimeException: Unable to obtain LocalDateTime from TemporalAccessor: {SecondOfMinute=8, MinuteOfHour=12, HourOfAmPm=2, OffsetSeconds=7200, NanoOfSecond=0, MilliOfSecond=0, MicroOfSecond=0},ISO resolved to 2021-06-07 of type java.time.format.Parsed
    at java.base/java.time.LocalDateTime.from(LocalDateTime.java:461)
    at java.base/java.time.format.Parsed.query(Parsed.java:235)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1948)
    ... 2 more
Caused by: java.time.DateTimeException: Unable to obtain LocalTime from TemporalAccessor: {SecondOfMinute=8, MinuteOfHour=12, HourOfAmPm=2, OffsetSeconds=7200, NanoOfSecond=0, MilliOfSecond=0, MicroOfSecond=0},ISO resolved to 2021-06-07 of type java.time.format.Parsed
    at java.base/java.time.LocalTime.from(LocalTime.java:431)
    at java.base/java.time.LocalDateTime.from(LocalDateTime.java:457)
    ... 4 more

I have check different resources but couldn't figure out the issue.

Upvotes: 1

Views: 1491

Answers (2)

deHaar
deHaar

Reputation: 18568

This does not look like a situation where one should use a LocalDateTime.

There are two alternatives: ZonedDateTime as you already use and OffsetDateTime, which you might want to use...

If you want to provide a zone, take the current moment in time in that zone and print the result without the explicit zone but with the offset of that zone, then I recommend to stick to a ZonedDateTime for most of the time.

Do something like this:

public static void main(String[] args) {
    // provide a zone
    String BERLIN_TIME_ZONE = "Europe/Berlin";
    // use ZonedDateTime.now() directly and pass the zone
    ZonedDateTime nowBerlin = ZonedDateTime.now(ZoneId.of(BERLIN_TIME_ZONE));
    // then create a String that actually keeps the offset and omits the zone
    String dateTimeInString = nowBerlin.format(
                                DateTimeFormatter.ISO_OFFSET_DATE_TIME
                              );
    // print the result
    System.out.println(dateTimeInString);
    
    // if you now want to convert back, use an OffsetDateTime
    OffsetDateTime nowPlus2h = OffsetDateTime.parse(dateTimeInString);
    // and print the result
    System.out.println(nowPlus2h.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
    // if you want it to not just be a time of 2 hours offset, apply the zone again
    ZonedDateTime nowAgainBerlin = nowPlus2h.atZoneSameInstant(
                                        ZoneId.of(BERLIN_TIME_ZONE)
                                    );
    // check if that's the same as parsed in the beginning
    if (nowAgainBerlin.equals(nowBerlin)) {
        System.out.println("Same instant in Berlin");
    } else {
        System.out.println("Something went wrong in Berlin");
    }
    
    // and if you really want to get a LocalDateTime, you can get it like this
    LocalDateTime localBerlin = nowBerlin.toLocalDateTime();
    LocalDateTime localAgainBerlin = nowPlus2h.toLocalDateTime();
    
    System.out.println(localBerlin + " == " + localAgainBerlin);
}

This just output

2021-06-07T15:16:47.259+02:00
2021-06-07T15:16:47.259+02:00
Same instant in Berlin
2021-06-07T15:16:47.259 == 2021-06-07T15:16:47.259

If you want to not have millis in your output, then define a formatter that suits your desires, but still, you won't be able to parse zones or offsets to a LocalDateTime.

Upvotes: 1

Michael
Michael

Reputation: 44150

Change the hour token from hh to HH. Lowercase h is 0-12, or what the docs call clock-hour-of-am-pm. Uppercase H is 0-23, or what the docs call hour-of-day.

While the formatter can oblige quite happily when outputting the date, when you try to read in the same string it creates an ambiguity. The parser doesn't know whether ...T01:... is supposed to be 1AM or PM. Since it cannot parse unambiguously, it fails.

Alternatively, add something into your format string for AM/PM (you can use a for that). Then the parser will be able to unambiguously interpret an hour such as 01.

Seems you also want to change LocalDateTime.parse to ZonedDateTime.parse so you're not throwing away the zone.

Upvotes: 2

Related Questions