Why can't OffsetDateTime parse '2016-08-24T18:38:05.507+0000' in Java 8
Asked Answered
M

5

8

The expression

OffsetDateTime.parse("2016-08-24T18:38:05.507+0000")

results in the following error:

java.time.format.DateTimeParseException: Text '2016-08-24T18:38:05.507+0000' could not be parsed at index 23

On the other hand,

OffsetDateTime.parse("2016-08-24T18:38:05.507+00:00")

works as expected.

DateTimeFormatter's doc page mentions zone offsets without colons as examples. What am I doing wrong? I'd rather not mangle my date string to appease Java.

Murky answered 24/8, 2016 at 22:14 Comment(2)
In the meantime, I'm using OffsetDateTime.parse(dateString.replaceFirst("\\+(\\d{2})(\\d{2})", "+$1:$2"))Murky
I am immodest enough to recommend my own answer here.Oly
R
8

You are calling the following method.

public static OffsetDateTime parse(CharSequence text) {
    return parse(text, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}

It uses uses DateTimeFormatter.ISO_OFFSET_DATE_TIME as DateTimeFormatter which, as stated in the javadoc, does the following:

The ISO date-time formatter that formats or parses a date-time with an offset, such as '2011-12-03T10:15:30+01:00'.

If you want to parse a date with a different format as in 2016-08-24T18:38:05.507+0000 you should use OffsetDateTime#parse(CharSequence, DateTimeFormatter). The following code should solve your problem:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
OffsetDateTime.parse("2016-08-24T18:38:05.507+0000", formatter);
Receptacle answered 24/8, 2016 at 22:47 Comment(1)
Mine is not working, it is really taking me in cyclesHutchens
I
5

Update

Thanks to Ole V.V. for suggesting this simpler pattern:

DateTimeFormatter dtf = new DateTimeFormatterBuilder()
                        .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
                        .appendPattern("[XXX][XX][X]")
                        .toFormatter(Locale.ENGLISH);

The original answer is still useful if the units (e.g. month, day, hour etc.) can be in single-digit or double-digit. This alternative pattern will fail in case units are in single-digit.

Original answer

The solution is to use a DateTimeFormatter with optional patterns. The DateTimeFormatter allows us to specify optional patterns in the square bracket.

Demo:

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern(
                "u-M-d'T'H:m:s[.[SSSSSSSSS][SSSSSSSS][SSSSSSS][SSSSSS][SSSSS][SSSS][SSS][SS][S]][XXX][XX][X]",
                Locale.ENGLISH);
        
        // Test
        Stream.of(
                "2021-07-22T20:10:15+0000",
                "2021-07-22T20:10:15+00:00",
                "2021-07-22T20:10:15+00",
                "2021-07-22T20:10:15.123456789+0000",
                "2021-07-22T20:10:15.12345678+0000",
                "2021-07-22T20:10:15.123+0000",
                "2021-07-22T20:10:15.1+0000"                
        ).forEach(s -> System.out.println(OffsetDateTime.parse(s, dtf)));
    }
}

Output:

2021-07-22T20:10:15Z
2021-07-22T20:10:15Z
2021-07-22T20:10:15Z
2021-07-22T20:10:15.123456789Z
2021-07-22T20:10:15.123456780Z
2021-07-22T20:10:15.123Z
2021-07-22T20:10:15.100Z

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 the modern Date-Time API from Trail: Date Time.


Check the documentation page of DateTimeFormatter for the complete list of pattern letters.

Inextensible answered 22/7, 2021 at 19:13 Comment(0)
W
2

Although DateTimeFormatter's pattern language does not provide a code for zone offsets that fail to accommodate your no-colon form, that does not imply that the pre-defined instances that handle zone offsets accept the no-colon form. The one-arg version of OffsetDateTime.parse() specifies that it uses DateTimeFormatter.ISO_OFFSET_DATE_TIME as its formatter, and that formatter's docs specify that it supports three formats, as described in the docs of ZoneOffset.getId(). None of those formats (which are drawn from ISO-8601) is consistent with your no-colon form.

But not to worry: just use the two-arg from of OffsetDateTime.parse(), providing an appropriate formatter. That's a bit less convenient, but quite doable.

Waggon answered 24/8, 2016 at 22:31 Comment(0)
S
2

Paypal incorrectly sends the offsets as +0000 which is actually contradicting their spec https://developer.paypal.com/docs/api/transaction-search/v1/ that says it has to be in an Internet date/time format where the offset is written as

 time-numoffset  = ("+" / "-") time-hour ":" time-minute

The : is required.

To work around this, a custom date time formatter should be created, but avoid using the simple pattern like yyyy-MM-dd'T'HH:mm:ssZ because it will fail if milliseconds suddenly appear in the output.

public static final DateTimeFormatter PAYPAL_DATE_TIME_FORMAT = new DateTimeFormatterBuilder()
    .parseCaseInsensitive()
    .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
    .parseLenient()
    .appendPattern("Z")
    .parseStrict()
    .toFormatter();

This is one way of doing it, but it has a flaw in that when Paypal corrects their output it won't be able to parse the : offset correctly.

Also Paypal does not support nanos so you should also do .truncatedTo(SECONDS) before sending it to their APIs

Single answered 22/7, 2021 at 4:26 Comment(0)
N
1

The default format is expected to be DateTimeFormatter.ISO_OFFSET_DATE_TIME defined with the following value for the zone offset:

static final OffsetIdPrinterParser INSTANCE_ID_Z = new OffsetIdPrinterParser("+HH:MM:ss", "Z");
Nonreturnable answered 24/8, 2016 at 22:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.