John Caron
John Caron

Reputation: 1387

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?

Upvotes: 2

Views: 540

Answers (2)

Basil Bourque
Basil Bourque

Reputation: 338356

W3C ISO 8601

That document is a self-declared “profile” of the ISO 8601 standard, not the standard itself.

This seems to be such a common use case, im surprised its not part of the standard library

The java.time classes do indeed use the ISO 8601 formats by default when parsing text.

There is no universal parser because context matters. A year, a year-month, a date, a time-of-day, a date with time-of-day, a date with time-of-day with offset, a date with time-of-day with time zone, are all different animals. You would not ordinarily expect values of different types to arrive arbitrarily.

If you really do have such a collection of arbitrary types, then simply perform a series of parse attempts. Trap for the DateTimeParseException exception. If parsing with one java.time class fails, move on to the next java.time class.

You said:

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

How would you turn a year-only value into a ZonedDateTime object? What should be the month and day values? Zero is not acceptable there. What time zone? You’ll need to specify your desired zone. What time-of-day? Zeros for time does not work as not all days in some zones start the day at 00:00:00.

For these reasons, a universal parser is not possible. You need to perform the parsing according to the business rules of your particular app for your particular input data.

You can come closer to your desired universal parser by establishing default values with the DateTimeFormatterBuilder class. See Answer by Avinash.

Upvotes: 4

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 79025

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.

Upvotes: 3

Related Questions