I am currently working with Java's DateTimeFormatter
to parse ISO 8601 formatted timestamps, particularly those containing fractional seconds. While experimenting with different timestamp formats, I noticed some unexpected behavior regarding how the formatter handles optional fractional seconds.
Specifically, I am curious about the leniency of the parser when it comes to the number of digits in the fractional seconds. My implementation allows for timestamps with 9 digits for fractional seconds, yet the parser successfully handles timestamps with only 8 digits while failing for those with 7 or fewer. This has led me to wonder if there is an underlying reason for this behavior, whether it is part of the design of the DateTimeFormatter, and if it is documented anywhere.
I wrote a test using the following code:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
public class DateTimeExample {
public static void main(String[] args) {
String[] timestamps = {
"2023-10-05T15:14:29.123456789Z", // 9 digits
"2023-10-05T15:14:29.12345678Z", // 8 digits
"2023-10-05T15:14:29.1234567Z", // 7 digits
"2023-10-05T15:14:29.123456Z", // 6 digits
"2023-10-05T15:14:29.12345Z", // 5 digits
"2023-10-05T15:14:29.1234Z", // 4 digits
"2023-10-05T15:14:29.123Z", // 3 digits
"2023-10-05T15:14:29.12Z", // 2 digits
"2023-10-05T15:14:29.1Z", // 1 digit
"2023-10-05T15:14:29Z" // no fractional seconds
};
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSSSSSSSS]'Z'");
for (String timestamp : timestamps) {
try {
LocalDateTime dateTime = LocalDateTime.parse(timestamp, formatter);
System.out.println("Parsed date: " + dateTime);
} catch (DateTimeParseException e) {
System.err.println("Failed to parse: " + timestamp + " - " + e.getMessage());
}
}
}
}
Observations
When I run this code, this is the output:
Parsed date: 2023-10-05T15:14:29.123456789
Parsed date: 2023-10-05T15:14:29.123456780
Failed to parse: 2023-10-05T15:14:29.1234567Z - Text '2023-10-05T15:14:29.1234567Z' could not be parsed at index 19
Failed to parse: 2023-10-05T15:14:29.123456Z - Text '2023-10-05T15:14:29.123456Z' could not be parsed at index 19
Failed to parse: 2023-10-05T15:14:29.12345Z - Text '2023-10-05T15:14:29.12345Z' could not be parsed at index 19
Failed to parse: 2023-10-05T15:14:29.1234Z - Text '2023-10-05T15:14:29.1234Z' could not be parsed at index 19
Failed to parse: 2023-10-05T15:14:29.123Z - Text '2023-10-05T15:14:29.123Z' could not be parsed at index 19
Failed to parse: 2023-10-05T15:14:29.12Z - Text '2023-10-05T15:14:29.12Z' could not be parsed at index 19
Failed to parse: 2023-10-05T15:14:29.1Z - Text '2023-10-05T15:14:29.1Z' could not be parsed at index 19
Parsed date: 2023-10-05T15:14:29
It successfully parses timestamps with 9 digits for fractional seconds or no fractional part, which is the expected behaviour. But why does it also work with 8 digits for fractional part? My conclusion from this behaviour is that the DateTimeFormatter is lenient with upto one extra digit in the pattern. Is that correct, if so, are there any relevant documentations that I can refer?
It looks like it was a bug in older versions of Java. The bug was reproducible with Java 11 in my system and with Java 12 on IdeOne (currently it uses Java 12). I tested it also with Java 17 in my system but the bug did not appear, as also can be seen in the screenshot shared by user85421.
There are two problems with your code:
timestamps
have strings with a time zone offset of Z
i.e., +00:00
. Therefore, you should parse it into an OffsetDateTime
rather than a LocalDateTime
. Since they are all in ISO 8601 format, you do not need to use a DateTimeFormatter
explicitly, as shown in the below demo.'Z'
in a date-time parsing/formatting pattern because 'Z'
is a character literal while Z
is a pattern character specifying time zone offset. To parse a string representing a time zone offset, you must use X
(or XX
or XXX
depending on the requirement).Demo:
public class Main {
public static void main(String[] args) {
System.out.println("Java Version: " + System.getProperty("java.version"));
String[] timestamps = {
"2023-10-05T15:14:29.123456789Z", // 9 digits
"2023-10-05T15:14:29.12345678Z", // 8 digits
"2023-10-05T15:14:29.1234567Z", // 7 digits
"2023-10-05T15:14:29.123456Z", // 6 digits
"2023-10-05T15:14:29.12345Z", // 5 digits
"2023-10-05T15:14:29.1234Z", // 4 digits
"2023-10-05T15:14:29.123Z", // 3 digits
"2023-10-05T15:14:29.12Z", // 2 digits
"2023-10-05T15:14:29.1Z", // 1 digit
"2023-10-05T15:14:29Z" // no fractional seconds
};
for (String timestamp : timestamps) {
try {
System.out.println("Parsed date: " + OffsetDateTime.parse(timestamp));
} catch (DateTimeParseException e) {
System.err.println("Failed to parse: " + timestamp + " - " + e.getMessage());
}
}
}
}
Output on my system with Java 17:
Java Version: 17.0.7
Parsed date: 2023-10-05T15:14:29.123456789Z
Parsed date: 2023-10-05T15:14:29.123456780Z
Parsed date: 2023-10-05T15:14:29.123456700Z
Parsed date: 2023-10-05T15:14:29.123456Z
Parsed date: 2023-10-05T15:14:29.123450Z
Parsed date: 2023-10-05T15:14:29.123400Z
Parsed date: 2023-10-05T15:14:29.123Z
Parsed date: 2023-10-05T15:14:29.120Z
Parsed date: 2023-10-05T15:14:29.100Z
Parsed date: 2023-10-05T15:14:29Z
For learners: Learn more about the modern date-time API from Trail: Date Time.