Search code examples
javatimeiso8601

I need a java.time parser that can handle any valid W3C ISO 8601 date/time string


There doesn't seem to be a built-in java.time parser that can handle any valid W3C ISO 8601 date/time string.

It should return a ZonedDateTime, with missing fields set to 0 as specified in the W3C spec.

This seems to be such a common use case, im surprised its not part of the standard library, and i imagine its been solved multiple times outside the library. Anyone have solutions?


Solution

  • DateTimeFormatterBuilder#parseDefaulting

    The examples in your link, W3C ISO 8601 are OffsetDateTime and not ZonedDateTime. A ZonedDateTime require a timezone ID e.g. Europe/London. Also, JDBC 4.2 supports OffsetDateTime, not ZonedDateTime.

    You can use DateTimeFormatterBuilder#parseDefaulting to parse your string into an OffsetDateTime with default values.

    Demo:

    import java.time.LocalDate;
    import java.time.OffsetDateTime;
    import java.time.ZoneId;
    import java.time.format.DateTimeFormatter;
    import java.time.format.DateTimeFormatterBuilder;
    import java.time.temporal.ChronoField;
    import java.util.Locale;
    import java.util.stream.Stream;
    
    public class Main {
        public static void main(String[] args) {
            ZoneId zoneId = ZoneId.systemDefault();
            LocalDate now = LocalDate.now(zoneId);
            
            DateTimeFormatter dtf = new DateTimeFormatterBuilder()
                    .appendPattern("uuuu[-MM[-dd]]['T'HH[:mm[:ss[.SSSSSSSSS][.SSSSSSSS][.SSSSSSS][.SSSSSS][.SSSSS][.SSSS][.SSS][.SS][.S]]]][XXX]")
                    .parseDefaulting(ChronoField.MONTH_OF_YEAR, now.getMonthValue())
                    .parseDefaulting(ChronoField.DAY_OF_MONTH, now.getDayOfMonth())
                    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
                    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
                    .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
                    .parseDefaulting(ChronoField.NANO_OF_SECOND, 0)     
                    .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
                    .toFormatter(Locale.ENGLISH);
            
            //Test          
            Stream.of(
                        "1997",
                        "1997-07",
                        "1997-07-16",
                        "1997-07-16T19:20+01:00",
                        "1997-07-16T19:20:30+01:00",
                        "1997-07-16T19:20:30.45+01:00"
            ).forEach(s -> System.out.println(OffsetDateTime.parse(s, dtf)));
        }
    }
    

    Output:

    1997-05-21T00:00Z
    1997-07-21T00:00Z
    1997-07-16T00:00Z
    1997-07-16T19:20+01:00
    1997-07-16T19:20:30+01:00
    1997-07-16T19:20:30.450+01:00
    

    The Z in the output is the timezone designator for zero-timezone offset. It stands for Zulu and specifies the Etc/UTC timezone (which has the timezone offset of +00:00 hours).

    Learn more about java.time, the modern date-time API* from Trail: Date Time.


    * For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.