Reputation: 510
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.
Upvotes: 8
Views: 7101
Reputation: 79055
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.
Upvotes: 5
Reputation: 41280
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
Upvotes: 2
Reputation: 2120
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);
Upvotes: 8
Reputation: 180181
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.
Upvotes: 2
Reputation: 10082
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");
Upvotes: 1