Search code examples
datetimejava-8java-timeiso8601datetime-parsing

Java 8 Parse ISO-8601 date ignoring presence (or absence) of timezone


My application should be able to parse date ignoring timezone (I always know for sure that it is UTC). The problem is that the date might come in both following forms -

2017-09-11T12:44:07.793Z

0001-01-01T00:00:00

I can parse the first one using LocalDateTime, and the second one using Instant class. Is there a way to do that using a single mechanism?

P.S. I'm trying to avoid hardcoding Z at the end of the input string


Solution

  • If the Z offset is optional, you can use a java.time.format.DateTimeFormatterBuilder with an optional section:

    DateTimeFormatter fmt = new DateTimeFormatterBuilder()
        // date/time
        .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
        // optional offset
        .optionalStart().appendOffsetId()
        // create formatter
        .toFormatter();
    

    Then you can use the parseBest method, with a list of TemporalQuery's that tries to create the correspondent object. Then you check the return type and act accordingly:

    Instant instant = null;
    // tries to create Instant, and if it fails, try a LocalDateTime
    TemporalAccessor parsed = fmt.parseBest("2017-09-11T12:44:07.793Z", Instant::from, LocalDateTime::from);
    if (parsed instanceof Instant) {
        instant = (Instant) parsed;
    } else if (parsed instanceof LocalDateTime) {
        // convert LocalDateTime to UTC instant
        instant = ((LocalDateTime) parsed).atOffset(ZoneOffset.UTC).toInstant();
    }
    System.out.println(instant); // 2017-09-11T12:44:07.793Z
    

    Running with the second input (0001-01-01T00:00:00) produces the Instant equivalent to 0001-01-01T00:00:00Z.

    In the example above, I used just Instant::from and LocalDateTime::from, so the formatter tries to first create an Instant. If it's not possible, then it tries to create a LocalDateTime. You can add as many types you want to that list (for example, I could add ZonedDateTime::from, and if a ZonedDateTime is created, I could just convert to Instant using toInstant() method).


    As you know for sure that the input is always in UTC, you can also set it directly in the formatter:

    DateTimeFormatter fmt = new DateTimeFormatterBuilder()
        // date/time
        .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
        // optional offset
        .optionalStart().appendOffsetId()
        // create formatter with UTC
        .toFormatter().withZone(ZoneOffset.UTC);
    

    So you can parse it directly to Instant:

    System.out.println(Instant.from(fmt.parse("2017-09-11T12:44:07.793Z"))); // 2017-09-11T12:44:07.793Z
    System.out.println(Instant.from(fmt.parse("0001-01-01T00:00:00"))); // 0001-01-01T00:00:00Z