How to parse ZonedDateTime with default zone?

31,561

Solution 1

Since the ISO_ZONED_DATE_TIME formatter expects zone or offset information, parsing fails. You'll have to make a DateTimeFormatter that has optional parts for both the zone information and the time part. It's not too hard reverse engineering the ZonedDateTimeFormatter and adding optional tags.

Then you parse the String using the parseBest() method of the formatter. Then, for suboptimal parse results you can create the ZonedDateTime using any default you want.

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
        .parseCaseInsensitive()
        .append(ISO_LOCAL_DATE)
        .optionalStart()           // time made optional
        .appendLiteral('T')
        .append(ISO_LOCAL_TIME)
        .optionalStart()           // zone and offset made optional
        .appendOffsetId()
        .optionalStart()
        .appendLiteral('[')
        .parseCaseSensitive()
        .appendZoneRegionId()
        .appendLiteral(']')
        .optionalEnd()
        .optionalEnd()
        .optionalEnd()
        .toFormatter();

TemporalAccessor temporalAccessor = formatter.parseBest(value, ZonedDateTime::from, LocalDateTime::from, LocalDate::from);
if (temporalAccessor instanceof ZonedDateTime) {
    return ((ZonedDateTime) temporalAccessor);
}
if (temporalAccessor instanceof LocalDateTime) {
    return ((LocalDateTime) temporalAccessor).atZone(ZoneId.systemDefault());
}
return ((LocalDate) temporalAccessor).atStartOfDay(ZoneId.systemDefault());

Solution 2

The formatter has a withZone() method that can be called to provide the missing time-zone.

ZonedDateTime.parse(
    value,
    DateTimeFormatter.ISO_ZONED_DATE_TIME.withZone(ZoneId.systemDefault()))

Bear in mind that there was a bug, so you need 8u20 or later for it to work fully.

Share:
31,561
Sergey Ponomarev
Author by

Sergey Ponomarev

I'm Java/Grails dev. Interested in Web development, IntelliJ plugins and especially inspections, FOSS contributor. Programming Blog LinkedIn profile - feel free to connect to my network Home page GitHub profile

Updated on July 09, 2022

Comments

  • Sergey Ponomarev
    Sergey Ponomarev almost 2 years

    How to parse ZoneDateTime from string that doesn't contain zone and others fields?

    Here is test in Spock to reproduce:

    import spock.lang.Specification
    import spock.lang.Unroll
    
    import java.time.ZoneId
    import java.time.ZoneOffset
    import java.time.ZonedDateTime
    import java.time.format.DateTimeFormatter
    
    @Unroll
    class ZonedDateTimeParsingSpec extends Specification {
        def "DateTimeFormatter.ISO_ZONED_DATE_TIME parsing incomplete date: #value #expected"() {
            expect:
            ZonedDateTime.parse(value, DateTimeFormatter.ISO_ZONED_DATE_TIME) == expected
            where:
            value                           | expected
            '2014-04-23T04:30:45.123Z'      | ZonedDateTime.of(2014, 4, 23, 4, 30, 45, 123_000_000, ZoneOffset.UTC)
            '2014-04-23T04:30:45.123+01:00' | ZonedDateTime.of(2014, 4, 23, 4, 30, 45, 123_000_000, ZoneOffset.ofHours(1))
            '2014-04-23T04:30:45.123'       | ZonedDateTime.of(2014, 4, 23, 4, 30, 45, 123_000_000, ZoneId.systemDefault())
            '2014-04-23T04:30'              | ZonedDateTime.of(2014, 4, 23, 4, 30, 0, 0, ZoneId.systemDefault())
            '2014-04-23'                    | ZonedDateTime.of(2014, 4, 23, 0, 0, 0, 0, ZoneId.systemDefault())
        }
    }
    

    First two test passed, all others failed with DateTimeParseException:

    • '2014-04-23T04:30:45.123' could not be parsed at index 23
    • '2014-04-23T04:30' could not be parsed at index 16
    • '2014-04-23' could not be parsed at index 10

    How can I parse incomplete dates with time and zone setted to default?

  • Sergey Ponomarev
    Sergey Ponomarev over 9 years
    The method withZone() always overrides zone even if it contains in string
  • bowmore
    bowmore over 9 years
    @stokito : the problem occurs on strings that have no zone information to be parsed.
  • Sergey Ponomarev
    Sergey Ponomarev over 9 years
    @bowmore the problem is that I need "default or preferred time zone" instead of overriding it
  • Sergey Ponomarev
    Sergey Ponomarev over 9 years
    Thanks, it's looks better, but doesn't solve a problem: <pre> Caused by: java.time.DateTimeException: Unable to obtain ZoneId from TemporalAccessor: {},ISO resolved to 2014-04-23T04:30:45.123 of type java.time.format.Parsed </pre> So, it fails again because I can't set Default timezone for parser. Same thing for other parts, for examle date. For example, how can parse a string like '23:45' to ZonedDateTime with current date and system time zone?
  • bowmore
    bowmore over 9 years
    It ran fine using java 8u25 for all 5 of the example string in the question. So, not sure what your problem is.
  • Sergey Ponomarev
    Sergey Ponomarev over 9 years
    problem is that I would like to get ZonedDateTime insted of LocalDateTime
  • bowmore
    bowmore over 9 years
    But my code returns a ZonedDateTime in all cases : all three return statments return a ZonedDateTime.
  • Sergey Ponomarev
    Sergey Ponomarev over 9 years
    Aha, yes, you right, it works, and thanks I will use it :) But anyway, I would prefer to have some more obvious and simple way to define format. Unfortunately as I see it JavaTime doesn't provide it currently
  • bowmore
    bowmore over 9 years
    You could use a pattern to create the formatter using the ofPattern() method. In a pattern you mark optional parts with square brackets