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.
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);
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With