Jason
Jason

Reputation: 1431

DateTimeFormatter and ISO 8601 Strings

I'm working with a RESTful api that returns dates in ISO-8601 format with offsets, an example is shown below...

 2019-12-30T00:00:00.000+00:00

I'm using the below method to convert the date String into a passed format, It uses DateTimeFormatter.ISO_DATE_TIME, I chose this based on the docs from oracle that state "This returns an immutable formatter capable of formatting and parsing the ISO-8601 extended local or offset date-time format"

 public static String formatISODateTime(String isoString, String format) {

    LocalDate date = null;
    String returnDate = null;
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME;

    try{
        date = LocalDate.parse(isoString, dateTimeFormatter);
        returnDate = date.format(DateTimeFormatter.ofPattern(format));
    } catch(DateTimeParseException e) {         
        // do something
    } catch(DateTimeException e) {          
        // do something
    }

    return returnDate;
}

Below is a simple test of this method...

 public static void main(String[] args) {

    System.out.println(BaseConverter.formatISODateTime("2019-12-27T00:00:00.000+00:00", "MM/dd/yyyy"));
    System.out.println(BaseConverter.formatISODateTime("2019-12-27T00:00:00.000+18:00", "MM/dd/yyyy"));
    System.out.println(BaseConverter.formatISODateTime("2019-12-27T00:00:00.000-18:00", "MM/dd/yyyy"));
}

Here's the output...

 12/27/2019
 12/27/2019
 12/27/2019

I'm struggling to understand why the offsets (-/+ 18) don't produce different dates?

Upvotes: 1

Views: 2512

Answers (2)

Joop Eggen
Joop Eggen

Reputation: 109547

A LocaleDate/LocalDateTime is zone agnostic. You gave as input an OffsetDateTime (more realistic would be a ZonedDateTime with day time savings). The offset will be discarded for a LocalDateTime.

To show the effects, I use the format "MM/dd/yyyy HH:mm:ss" and the time part.

For OffsetDateTime or ZonedDateTime the zone would be discarded in the output too, but it is available, and selecting the Universal Time Coordinated, Zulu time, one needs to move the zone info in the time:

public static String formatISODateTime(String isoString, String format) {
    String returnDate = null;
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME;

    try{
        // 1
        OffsetDateTime odate = OffsetDateTime.parse(isoString, dateTimeFormatter);
        returnDate = odate.atZoneSameInstant(ZoneId.of("UTC")).format(
                DateTimeFormatter.ofPattern(format));

        // 2
        ZonedDateTime zdate = ZonedDateTime.parse(isoString, dateTimeFormatter);
        returnDate = zdate.withZoneSameInstant(ZoneId.of("UTC")).format(
                DateTimeFormatter.ofPattern(format));
    } catch(DateTimeException e) {
        e.printStackTrace();
    }
    return returnDate;
}

12/27/2019 00:00:00
12/26/2019 06:00:00
12/27/2019 18:00:00

Upvotes: 1

Michael
Michael

Reputation: 44130

You're parsing into a local date.

Despite representing different instants along a timeline (due to the offsets), in local terms, 27th December in China is the same as 27th December in Venezuela. That is to say that information is irreversibly lost when you instantiate LocalDates.

If you want to print different dates, you presumably are expecting a conversion to happen to UTC.

First you will need to parse into a structure which retains both time and zone information (ZonedDateTime does this), then you can convert to UTC manually.

public static String formatISODateTime(String isoString, String format) {

    ZonedDateTime date = null;
    String returnDate = null;
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME;

    try {
        date = ZonedDateTime.parse(isoString, dateTimeFormatter).withZoneSameInstant(ZoneOffset.UTC);
        returnDate = date.format(DateTimeFormatter.ofPattern(format));
    } catch(DateTimeParseException e) {
        // do something
    } catch(DateTimeException e) {
        // do something
    }

    return returnDate;
}

This prints

12/27/2019
12/26/2019
12/27/2019

Upvotes: 4

Related Questions