Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Post History

80%
+6 −0
Q&A How to parse a date with more than 3 decimal digits in the fractions of second?

The solution depends on the Java version you're using. First, let's see the solution for earlier versions, that doesn't use SimpleDateFormat. Then we'll see why the problem happens and alternatives...

posted 2y ago by hkotsubo‭  ·  edited 2y ago by hkotsubo‭

Answer
#4: Post edited by user avatar hkotsubo‭ · 2021-08-20T16:33:39Z (over 2 years ago)
  • The solution depends on the Java version you're using. First, let's see the solution for earlier versions, that doesn't use `SimpleDateFormat`. Then we'll see why the problem happens and alternatives for older versions.
  • ---
  • # JDK >= 8
  • For JDK >= 8, you can (_should/must?_) use the [`java.time` API](https://docs.oracle.com/javase/tutorial/datetime/index.html). It supplants the old legacy API (`Date`, `Calendar`, `SimpleDateFormat`) and should be preferred for newer projects (unless you're stuck with the legacy for any reason).
  • The old API has milissecond precision and can't correctly handle more than 3 decimal digits. The new API has nanosecond precision and can handle up to 9 decimal digits without any problem.
  • Another difference is that in `java.time` there are [lots of different types](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html#h1) to represent dates and times. In your case, the input string has date and time, so the best fit is probably a [`java.time.LocalDateTime`](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html):
  • ```java
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.123456789");
  • System.out.println(dt); // 2021-10-01T10:30:45.123456789
  • ```
  • As the input string is in [ISO 8601 format](https://en.wikipedia.org/wiki/ISO_8601), it can be parsed directly. But you can also use a [`java.time.format.DateTimeFormatter`](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html):
  • ```java
  • DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS");
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.123456789", parser);
  • ```
  • The difference is that this `DateTimeFormatter` accepts **exactly** 9 decimal digits, while the one-arg `parse` method above uses a built-in parser that accepts from 0 to 9 digits:
  • ```java
  • // OK, it accepts from 0 to 9 decimal digits
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.12345");
  • // error, it accepts exactly 9 decimal digits
  • DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS");
  • dt = LocalDateTime.parse("2021-10-01T10:30:45.12345", parser); // DateTimeParseException
  • ```
  • Anyway, if the input string is in ISO 8601 format, there's no need to build a custom `DateTimeFormatter` (unless you need to be strict about the exact number of decimal digits).
  • But what if I need a different format (let's say, "day/month/year"), and the flexibility of a variable number of decimal digits?
  • In that case, you can use a `java.time.format.DateTimeFormatterBuilder`:
  • ```java
  • DateTimeFormatter parser = new DateTimeFormatterBuilder()
  • // let's say I need a different date format
  • .appendPattern("dd/MM/uuuu HH:mm:ss")
  • // fraction of second, from 0 to 9 digits and the decimal separator
  • .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
  • .toFormatter();
  • LocalDateTime dt = LocalDateTime.parse("01/10/2021 10:30:45.12345", parser);
  • // output uses toString(), which always shows in ISO 8601
  • System.out.println(dt); // 2021-10-01T10:30:45.123450
  • // using the same DateTimeFormatter, it uses the specific format
  • System.out.println(dt.format(parser)); // 01/10/2021 10:30:45.12345
  • ```
  • Showing all the parsing/formatting options is beyond the scope of the question, so please [refer to the documentation](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html) to see all the possibilities. The point here is: if you need to handle up to 9 decimal digits in the fraction of seconds, `java.time` is the best choice, IMO.
  • Depending on the input, different types might be needed, as the API provides lots of options (such as `LocalTime` for time-only data, `ZonedDateTime` if you need to deal with timezones, etc). All of them can handle up to 9 decimal digits. And if you "need" a `Date` or `Calendar` instance (maybe because you're using legacy code that works only with those classes), [there are ways to convert to/from them](https://docs.oracle.com/javase/tutorial/datetime/iso/legacy.html).
  • ---
  • # JDK <= 7
  • If you're using JDK <= 7 (or is using a newer Java version, but has legacy code that still uses the old API), you can't use `SimpleDateFormat` directly like you did, because it'll return incorrect dates.
  • As previously said, the old date/time API (`Date`, `Calendar`, `SimpleDateFormat`) has millisecond precision and can't handle more than 3 decimal digits in the fraction of seconds.
  • ### But `SimpleDateFormat` didn't give any errors...
  • Yes, `SimpleDateFormat` is known to be **very** lenient and accept almost everything as input, and "tries its best" to give you a `Date`, even if it leads to wrong results (you can see some examples [here](https://eyalsch.wordpress.com/2009/05/29/sdf/) and [here](https://www.javaspecialists.eu/archive/Issue172-Wonky-Dating.html)).
  • In this specific case, the `S` token used in `SimpleDateFormat` pattern means "milliseconds" (while in `java.time` API, it means "fraction of seconds" - with nanosecond precision: same letter, different meaning, that's why it works in one API while failing in the other).
  • Therefore, when `SSSSSSSSS` is used with `SimpleDateFormat`, it means that 9 digits will be parsed and set to the milliseconds value. So in the string `2021-10-01T10:30:45.123456789`, the parser reads it as "123456789 milliseconds", which in turn is equivalent to "34 hours, 17 minutes, 36 seconds and 789 milliseconds". And `SimpleDateFormat` **adds** this to the date/time values previously read. If that makes sense or not, it's a discussion for another day[^1]. But that's what happened, and it explains why the resulting `Date` has some values that are completely different from the input.
  • If you need to store all the decimal digits, you'll have to store them separately from the date. The best you can do is to store the whole fraction of seconds in another field/variable, and optionally keep the `Date` with the milliseconds (the first 3 decimal digits). Something like this:
  • ```java
  • String s = "2021-10-01T10:30:45.12345";
  • // parse the date/time without the decimal digits
  • String[] parts = s.split("\\.");
  • SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
  • Date date = sdf.parse(parts[0]);
  • // check if there are decimal digits, store the value as nanoseconds
  • int nanos;
  • if (parts.length == 1) { // no decimal digits
  • nanos = 0;
  • } else {
  • // right-pad with zeroes, so the value is in nanoseconds
  • nanos = Integer.parseInt(String.format("%-9s", parts[1]).replaceAll(" ", "0"));
  • // The Date has no milliseconds, but we can add it
  • date.setTime(date.getTime() + (nanos / 1000000));
  • }
  • System.out.println(sdf.format(date)); // 2021-10-01T10:30:45
  • System.out.println(nanos); // 123450000
  • ```
  • You could also validate if `parts[1].length` is greater than 9 (and show an error message in that case, if the maximum precision you want to handle is 9 digits, of course), etc. But the general ideia is to store the fraction of seconds in a separate field, in case you don't want to lose precision.
  • If you don't mind losing precision and want to preserve just the first 3 decimal digits, you can use `date.setTime(date.getTime() + (nanos / 1000000))` as shown above, and then discard the `nanos` variable. This is needed because we used a pattern without milliseconds, so `SimpleDateFormat` sets its value to zero.
  • ### Alternative for JDK 6 and 7
  • If you're using JDK 6 or 7, it's possible to use the [Three-Ten Backport](https://www.threeten.org/threetenbp/), which provides a backport of the `java.time` API.
  • It's very similar to `java.time`, the difference is that all classes are in the `org.threeten.bp` package (the _names_ of the classes and their methods are the same, though, and all have nanosecond precision). One exception is the conversion to/from `Date`/`Calendar`: in Java >= 8 it's implemented as methods of those classes, but in the backport this is made by an [utilitary class](https://www.threeten.org/threetenbp/apidocs/org/threeten/bp/DateTimeUtils.html).
  • With the backport, the code is basically the same as above, with `DateTimeFormatter`, `LocalDateTime`, etc.
  • [^1]: IMO, it doesn't make any sense :-)
  • The solution depends on the Java version you're using. First, let's see the solution for earlier versions, that doesn't use `SimpleDateFormat`. Then we'll see why the problem happens and alternatives for older versions.
  • ---
  • # JDK >= 8
  • For JDK >= 8, you can (_should/must?_) use the [`java.time` API](https://docs.oracle.com/javase/tutorial/datetime/index.html). It supplants the old legacy API (`Date`, `Calendar`, `SimpleDateFormat`) and should be preferred for newer projects (unless you're stuck with the legacy for any reason).
  • The old API has milissecond precision and can't correctly handle more than 3 decimal digits. The new API has nanosecond precision and can handle up to 9 decimal digits without any problem.
  • Another difference is that in `java.time` there are [lots of different types](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html#h1) to represent dates and times. In your case, the input string has date and time, so the best fit is probably a [`java.time.LocalDateTime`](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html):
  • ```java
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.123456789");
  • System.out.println(dt); // 2021-10-01T10:30:45.123456789
  • ```
  • As the input string is in [ISO 8601 format](https://en.wikipedia.org/wiki/ISO_8601), it can be parsed directly. But you can also use a [`java.time.format.DateTimeFormatter`](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html):
  • ```java
  • DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS");
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.123456789", parser);
  • ```
  • The difference is that this `DateTimeFormatter` accepts **exactly** 9 decimal digits, while the one-arg `parse` method above uses a built-in parser that accepts from 0 to 9 digits:
  • ```java
  • // OK, it accepts from 0 to 9 decimal digits
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.12345");
  • // error, it accepts exactly 9 decimal digits
  • DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS");
  • dt = LocalDateTime.parse("2021-10-01T10:30:45.12345", parser); // DateTimeParseException
  • ```
  • Anyway, if the input string is in ISO 8601 format, there's no need to build a custom `DateTimeFormatter` (unless you need to be strict about the exact number of decimal digits).
  • But what if I need a different format (let's say, "day/month/year"), and the flexibility of a variable number of decimal digits?
  • In that case, you can use a `java.time.format.DateTimeFormatterBuilder`:
  • ```java
  • DateTimeFormatter parser = new DateTimeFormatterBuilder()
  • // let's say I need a different date format
  • .appendPattern("dd/MM/uuuu HH:mm:ss")
  • // fraction of second, from 0 to 9 digits and the decimal separator
  • .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
  • .toFormatter();
  • LocalDateTime dt = LocalDateTime.parse("01/10/2021 10:30:45.12345", parser);
  • // output uses toString(), which always shows in ISO 8601
  • System.out.println(dt); // 2021-10-01T10:30:45.123450
  • // using the same DateTimeFormatter, it uses the specific format
  • System.out.println(dt.format(parser)); // 01/10/2021 10:30:45.12345
  • ```
  • Showing all the parsing/formatting options is beyond the scope of the question, so please [refer to the documentation](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html) to see all the possibilities. The point here is: if you need to handle up to 9 decimal digits in the fraction of seconds, `java.time` is the best choice, IMO.
  • Depending on the input, different types might be needed, as the API provides lots of options (such as `LocalTime` for time-only data, `ZonedDateTime` if you need to deal with timezones, etc). All of them can handle up to 9 decimal digits. And if you "need" a `Date` or `Calendar` instance (maybe because you're using legacy code that works only with those classes), [there are ways to convert to/from them](https://docs.oracle.com/javase/tutorial/datetime/iso/legacy.html).
  • ---
  • # JDK <= 7
  • If you're using JDK <= 7 (or is using a newer Java version, but has legacy code that still uses the old API), you can't use `SimpleDateFormat` directly like you did, because it'll return incorrect dates.
  • As previously said, the old date/time API (`Date`, `Calendar`, `SimpleDateFormat`) has millisecond precision and can't handle more than 3 decimal digits in the fraction of seconds.
  • ### But `SimpleDateFormat` didn't give any errors...
  • Yes, `SimpleDateFormat` is known to be **very** lenient and accept almost everything as input, and "tries its best" to give you a `Date`, even if it leads to wrong results (you can see some examples [here](https://eyalsch.wordpress.com/2009/05/29/sdf/) and [here](https://www.javaspecialists.eu/archive/Issue172-Wonky-Dating.html)).
  • In this specific case, the `S` token used in `SimpleDateFormat` pattern means "milliseconds" (while in `java.time` API, it means "fraction of seconds" - with nanosecond precision: same letter, different meaning, that's why it works in one API while failing in the other).
  • Therefore, when `SSSSSSSSS` is used with `SimpleDateFormat`, it means that 9 digits will be parsed and set to the milliseconds value. So in the string `2021-10-01T10:30:45.123456789`, the parser reads the last 9 digits as "123456789 milliseconds", which in turn is equivalent to "34 hours, 17 minutes, 36 seconds and 789 milliseconds". And `SimpleDateFormat` **adds** this to the date/time values previously read. If that makes sense or not, it's a discussion for another day[^1]. But that's what happened, and it explains why the resulting `Date` has some values that are completely different from the input.
  • If you need to store all the decimal digits, you'll have to store them separately from the date. The best you can do is to store the whole fraction of seconds in another field/variable, and optionally keep the `Date` with the milliseconds (the first 3 decimal digits). Something like this:
  • ```java
  • String s = "2021-10-01T10:30:45.12345";
  • // parse the date/time without the decimal digits
  • String[] parts = s.split("\\.");
  • SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
  • Date date = sdf.parse(parts[0]);
  • // check if there are decimal digits, store the value as nanoseconds
  • int nanos;
  • if (parts.length == 1) { // no decimal digits
  • nanos = 0;
  • } else {
  • // right-pad with zeroes, so the value is in nanoseconds
  • nanos = Integer.parseInt(String.format("%-9s", parts[1]).replaceAll(" ", "0"));
  • // The Date has no milliseconds, but we can add it
  • date.setTime(date.getTime() + (nanos / 1000000));
  • }
  • System.out.println(sdf.format(date)); // 2021-10-01T10:30:45
  • System.out.println(nanos); // 123450000
  • ```
  • You could also validate if `parts[1].length` is greater than 9 (and show an error message in that case, if the maximum precision you want to handle is 9 digits, of course), etc. But the general ideia is to store the fraction of seconds in a separate field, in case you don't want to lose precision.
  • If you don't mind losing precision and want to preserve just the first 3 decimal digits, you can use `date.setTime(date.getTime() + (nanos / 1000000))` as shown above, and then discard the `nanos` variable. This is needed because we used a pattern without milliseconds, so `SimpleDateFormat` sets its value to zero.
  • ### Alternative for JDK 6 and 7
  • If you're using JDK 6 or 7, it's possible to use the [Three-Ten Backport](https://www.threeten.org/threetenbp/), which provides a backport of the `java.time` API.
  • It's very similar to `java.time`, the difference is that all classes are in the `org.threeten.bp` package (the _names_ of the classes and their methods are the same, though, and all have nanosecond precision). One exception is the conversion to/from `Date`/`Calendar`: in Java >= 8 it's implemented as methods of those classes, but in the backport this is made by an [utilitary class](https://www.threeten.org/threetenbp/apidocs/org/threeten/bp/DateTimeUtils.html).
  • With the backport, the code is basically the same as above, with `DateTimeFormatter`, `LocalDateTime`, etc.
  • [^1]: IMO, it doesn't make any sense :-)
#3: Post edited by user avatar hkotsubo‭ · 2021-08-20T16:31:46Z (over 2 years ago)
  • The solution depends on the Java version you're using. First, let's see the solution for earlier versions, that doesn't use `SimpleDateFormat`. Then we'll see why the problem happens and alternatives for older versions.
  • ---
  • # JDK >= 8
  • For JDK >= 8, you can (_should/must?_) use the [`java.time` API](https://docs.oracle.com/javase/tutorial/datetime/index.html). It supplants the old legacy API (`Date`, `Calendar`, `SimpleDateFormat`) and should be preferred for newer projects (unless you're stuck with the legacy for any reason).
  • The old API has milissecond precision and can't correctly handle more than 3 decimal digits. The new API has nanosecond precision and can handle up to 9 decimal digits without any problem.
  • Another difference is that in `java.time` there are [lots of different types](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html#h1) to represent dates and times. In your case, the input string has date and time, so the best fit is probably a [`java.time.LocalDateTime`](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html):
  • ```java
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.123456789");
  • System.out.println(dt); // 2021-10-01T10:30:45.123456789
  • ```
  • As the input string is in [ISO 8601 format](https://en.wikipedia.org/wiki/ISO_8601), it can be parsed directly. But you can also use a [`java.time.format.DateTimeFormatter`](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html):
  • ```java
  • DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS");
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.123456789", parser);
  • ```
  • The difference is that this `DateTimeFormatter` accepts **exactly** 9 decimal digits, while the one-arg `parse` method above uses a built-in parser that accepts from 0 to 9 digits:
  • ```java
  • // OK, it accepts from 0 to 9 decimal digits
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.12345");
  • // error, it accepts exactly 9 decimal digits
  • DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS");
  • dt = LocalDateTime.parse("2021-10-01T10:30:45.12345", parser); // DateTimeParseException
  • ```
  • Anyway, if the input string is in ISO 8601 format, there's no need to build a custom `DateTimeFormatter` (unless you need to be strict about the exact number of decimal digits).
  • But what if I need a different format (let's say, "day/month/year"), and the flexibility of a variable number of decimal digits?
  • In that case, you can use a `java.time.format.DateTimeFormatterBuilder`:
  • ```java
  • DateTimeFormatter parser = new DateTimeFormatterBuilder()
  • // let's say I need a different date format
  • .appendPattern("dd/MM/uuuu HH:mm:ss")
  • // fraction of second, from 0 to 9 digits and the decimal separator
  • .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
  • .toFormatter();
  • LocalDateTime dt = LocalDateTime.parse("01/10/2021 10:30:45.12345", parser); // DateTimeParseException
  • // output uses toString(), which always shows in ISO 8601
  • System.out.println(dt); // 2021-10-01T10:30:45.123450
  • // using the same DateTimeFormatter, it uses the specific format
  • System.out.println(dt.format(parser)); // 01/10/2021 10:30:45.12345
  • ```
  • Showing all the parsing/formatting options is beyond the scope of the question, so please [refer to the documentation](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html) to see all the possibilities. The point here is: if you need to handle up to 9 decimal digits in the fraction of seconds, `java.time` is the best choice, IMO.
  • Depending on the input, different types might be needed, as the API provides lots of options (such as `LocalTime` for time-only data, `ZonedDateTime` if you need to deal with timezones, etc). All of them can handle up to 9 decimal digits. And if you "need" a `Date` or `Calendar` instance (maybe because you're using legacy code that works only with those classes), [there are ways to convert to/from them](https://docs.oracle.com/javase/tutorial/datetime/iso/legacy.html).
  • ---
  • # JDK <= 7
  • If you're using JDK <= 7 (or is using a newer Java version, but has legacy code that still uses the old API), you can't use `SimpleDateFormat` directly like you did, because it'll return incorrect dates.
  • As previously said, the old date/time API (`Date`, `Calendar`, `SimpleDateFormat`) has millisecond precision and can't handle more than 3 decimal digits in the fraction of seconds.
  • ### But `SimpleDateFormat` didn't give any errors...
  • Yes, `SimpleDateFormat` is known to be **very** lenient and accept almost everything as input, and "tries its best" to give you a `Date`, even if it leads to wrong results (you can see some examples [here](https://eyalsch.wordpress.com/2009/05/29/sdf/) and [here](https://www.javaspecialists.eu/archive/Issue172-Wonky-Dating.html)).
  • In this specific case, the `S` token used in `SimpleDateFormat` pattern means "milliseconds" (while in `java.time` API, it means "fraction of seconds" - with nanosecond precision: same letter, different meaning, that's why it works in one API while failing in the other).
  • Therefore, when `SSSSSSSSS` is used with `SimpleDateFormat`, it means that 9 digits will be parsed and set to the milliseconds value. So in the string `2021-10-01T10:30:45.123456789`, the parser reads it as "123456789 milliseconds", which in turn is equivalent to "34 hours, 17 minutes, 36 seconds and 789 milliseconds". And `SimpleDateFormat` **adds** this to the date/time values previously read. If that makes sense or not, it's a discussion for another day[^1]. But that's what happened, and it explains why the resulting `Date` has some values that are completely different from the input.
  • If you need to store all the decimal digits, you'll have to store them separately from the date. The best you can do is to store the whole fraction of seconds in another field/variable, and optionally keep the `Date` with the milliseconds (the first 3 decimal digits). Something like this:
  • ```java
  • String s = "2021-10-01T10:30:45.12345";
  • // parse the date/time without the decimal digits
  • String[] parts = s.split("\\.");
  • SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
  • Date date = sdf.parse(parts[0]);
  • // check if there are decimal digits, store the value as nanoseconds
  • int nanos;
  • if (parts.length == 1) { // no decimal digits
  • nanos = 0;
  • } else {
  • // right-pad with zeroes, so the value is in nanoseconds
  • nanos = Integer.parseInt(String.format("%-9s", parts[1]).replaceAll(" ", "0"));
  • // The Date has no milliseconds, but we can add it
  • date.setTime(date.getTime() + (nanos / 1000000));
  • }
  • System.out.println(sdf.format(date)); // 2021-10-01T10:30:45
  • System.out.println(nanos); // 123450000
  • ```
  • You could also validate if `parts[1].length` is greater than 9 (and show an error message in that case, if the maximum precision you want to handle is 9 digits, of course), etc. But the general ideia is to store the fraction of seconds in a separate field, in case you don't want to lose precision.
  • If you don't mind losing precision and want to preserve just the first 3 decimal digits, you can use `date.setTime(date.getTime() + (nanos / 1000000))` as shown above, and then discard the `nanos` variable. This is needed because we used a pattern without milliseconds, so `SimpleDateFormat` sets its value to zero.
  • ### Alternative for JDK 6 and 7
  • If you're using JDK 6 or 7, it's possible to use the [Three-Ten Backport](https://www.threeten.org/threetenbp/), which provides a backport of the `java.time` API.
  • It's very similar to `java.time`, the difference is that all classes are in the `org.threeten.bp` package (the _names_ of the classes and their methods are the same, though, and all have nanosecond precision). One exception is the conversion to/from `Date`/`Calendar`: in Java >= 8 it's implemented as methods of those classes, but in the backport this is made by an [utilitary class](https://www.threeten.org/threetenbp/apidocs/org/threeten/bp/DateTimeUtils.html).
  • With the backport, the code is basically the same as above, with `DateTimeFormatter`, `LocalDateTime`, etc.
  • [^1]: IMO, it doesn't make any sense :-)
  • The solution depends on the Java version you're using. First, let's see the solution for earlier versions, that doesn't use `SimpleDateFormat`. Then we'll see why the problem happens and alternatives for older versions.
  • ---
  • # JDK >= 8
  • For JDK >= 8, you can (_should/must?_) use the [`java.time` API](https://docs.oracle.com/javase/tutorial/datetime/index.html). It supplants the old legacy API (`Date`, `Calendar`, `SimpleDateFormat`) and should be preferred for newer projects (unless you're stuck with the legacy for any reason).
  • The old API has milissecond precision and can't correctly handle more than 3 decimal digits. The new API has nanosecond precision and can handle up to 9 decimal digits without any problem.
  • Another difference is that in `java.time` there are [lots of different types](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html#h1) to represent dates and times. In your case, the input string has date and time, so the best fit is probably a [`java.time.LocalDateTime`](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html):
  • ```java
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.123456789");
  • System.out.println(dt); // 2021-10-01T10:30:45.123456789
  • ```
  • As the input string is in [ISO 8601 format](https://en.wikipedia.org/wiki/ISO_8601), it can be parsed directly. But you can also use a [`java.time.format.DateTimeFormatter`](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html):
  • ```java
  • DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS");
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.123456789", parser);
  • ```
  • The difference is that this `DateTimeFormatter` accepts **exactly** 9 decimal digits, while the one-arg `parse` method above uses a built-in parser that accepts from 0 to 9 digits:
  • ```java
  • // OK, it accepts from 0 to 9 decimal digits
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.12345");
  • // error, it accepts exactly 9 decimal digits
  • DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS");
  • dt = LocalDateTime.parse("2021-10-01T10:30:45.12345", parser); // DateTimeParseException
  • ```
  • Anyway, if the input string is in ISO 8601 format, there's no need to build a custom `DateTimeFormatter` (unless you need to be strict about the exact number of decimal digits).
  • But what if I need a different format (let's say, "day/month/year"), and the flexibility of a variable number of decimal digits?
  • In that case, you can use a `java.time.format.DateTimeFormatterBuilder`:
  • ```java
  • DateTimeFormatter parser = new DateTimeFormatterBuilder()
  • // let's say I need a different date format
  • .appendPattern("dd/MM/uuuu HH:mm:ss")
  • // fraction of second, from 0 to 9 digits and the decimal separator
  • .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
  • .toFormatter();
  • LocalDateTime dt = LocalDateTime.parse("01/10/2021 10:30:45.12345", parser);
  • // output uses toString(), which always shows in ISO 8601
  • System.out.println(dt); // 2021-10-01T10:30:45.123450
  • // using the same DateTimeFormatter, it uses the specific format
  • System.out.println(dt.format(parser)); // 01/10/2021 10:30:45.12345
  • ```
  • Showing all the parsing/formatting options is beyond the scope of the question, so please [refer to the documentation](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html) to see all the possibilities. The point here is: if you need to handle up to 9 decimal digits in the fraction of seconds, `java.time` is the best choice, IMO.
  • Depending on the input, different types might be needed, as the API provides lots of options (such as `LocalTime` for time-only data, `ZonedDateTime` if you need to deal with timezones, etc). All of them can handle up to 9 decimal digits. And if you "need" a `Date` or `Calendar` instance (maybe because you're using legacy code that works only with those classes), [there are ways to convert to/from them](https://docs.oracle.com/javase/tutorial/datetime/iso/legacy.html).
  • ---
  • # JDK <= 7
  • If you're using JDK <= 7 (or is using a newer Java version, but has legacy code that still uses the old API), you can't use `SimpleDateFormat` directly like you did, because it'll return incorrect dates.
  • As previously said, the old date/time API (`Date`, `Calendar`, `SimpleDateFormat`) has millisecond precision and can't handle more than 3 decimal digits in the fraction of seconds.
  • ### But `SimpleDateFormat` didn't give any errors...
  • Yes, `SimpleDateFormat` is known to be **very** lenient and accept almost everything as input, and "tries its best" to give you a `Date`, even if it leads to wrong results (you can see some examples [here](https://eyalsch.wordpress.com/2009/05/29/sdf/) and [here](https://www.javaspecialists.eu/archive/Issue172-Wonky-Dating.html)).
  • In this specific case, the `S` token used in `SimpleDateFormat` pattern means "milliseconds" (while in `java.time` API, it means "fraction of seconds" - with nanosecond precision: same letter, different meaning, that's why it works in one API while failing in the other).
  • Therefore, when `SSSSSSSSS` is used with `SimpleDateFormat`, it means that 9 digits will be parsed and set to the milliseconds value. So in the string `2021-10-01T10:30:45.123456789`, the parser reads it as "123456789 milliseconds", which in turn is equivalent to "34 hours, 17 minutes, 36 seconds and 789 milliseconds". And `SimpleDateFormat` **adds** this to the date/time values previously read. If that makes sense or not, it's a discussion for another day[^1]. But that's what happened, and it explains why the resulting `Date` has some values that are completely different from the input.
  • If you need to store all the decimal digits, you'll have to store them separately from the date. The best you can do is to store the whole fraction of seconds in another field/variable, and optionally keep the `Date` with the milliseconds (the first 3 decimal digits). Something like this:
  • ```java
  • String s = "2021-10-01T10:30:45.12345";
  • // parse the date/time without the decimal digits
  • String[] parts = s.split("\\.");
  • SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
  • Date date = sdf.parse(parts[0]);
  • // check if there are decimal digits, store the value as nanoseconds
  • int nanos;
  • if (parts.length == 1) { // no decimal digits
  • nanos = 0;
  • } else {
  • // right-pad with zeroes, so the value is in nanoseconds
  • nanos = Integer.parseInt(String.format("%-9s", parts[1]).replaceAll(" ", "0"));
  • // The Date has no milliseconds, but we can add it
  • date.setTime(date.getTime() + (nanos / 1000000));
  • }
  • System.out.println(sdf.format(date)); // 2021-10-01T10:30:45
  • System.out.println(nanos); // 123450000
  • ```
  • You could also validate if `parts[1].length` is greater than 9 (and show an error message in that case, if the maximum precision you want to handle is 9 digits, of course), etc. But the general ideia is to store the fraction of seconds in a separate field, in case you don't want to lose precision.
  • If you don't mind losing precision and want to preserve just the first 3 decimal digits, you can use `date.setTime(date.getTime() + (nanos / 1000000))` as shown above, and then discard the `nanos` variable. This is needed because we used a pattern without milliseconds, so `SimpleDateFormat` sets its value to zero.
  • ### Alternative for JDK 6 and 7
  • If you're using JDK 6 or 7, it's possible to use the [Three-Ten Backport](https://www.threeten.org/threetenbp/), which provides a backport of the `java.time` API.
  • It's very similar to `java.time`, the difference is that all classes are in the `org.threeten.bp` package (the _names_ of the classes and their methods are the same, though, and all have nanosecond precision). One exception is the conversion to/from `Date`/`Calendar`: in Java >= 8 it's implemented as methods of those classes, but in the backport this is made by an [utilitary class](https://www.threeten.org/threetenbp/apidocs/org/threeten/bp/DateTimeUtils.html).
  • With the backport, the code is basically the same as above, with `DateTimeFormatter`, `LocalDateTime`, etc.
  • [^1]: IMO, it doesn't make any sense :-)
#2: Post edited by user avatar hkotsubo‭ · 2021-08-20T16:25:07Z (over 2 years ago)
  • The solution depends on the Java version you're using. First, let's see the solution for earlier versions, that doesn't use `SimpleDateFormat`. Then we'll see why the problem happens and alternatives for older versions.
  • ---
  • # JDK >= 8
  • For JDK >= 8, you can (_should/must?_) use the [`java.time` API](https://docs.oracle.com/javase/tutorial/datetime/index.html). It supplants the old legacy API (`Date`, `Calendar`, `SimpleDateFormat`) and should be preferred for newer projects (unless you're stuck with the legacy for any reason).
  • The old API has milissecond precision and can't correctly handle more than 3 decimal digits. The new API has nanosecond precision and can handle up to 9 decimal digits without any problem.
  • Another difference is that in `java.time` there are [lots of different types](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html#h1) to represent dates and times. In your case, the input string has date and time, so the best fit is probably a [`java.time.LocalDateTime`](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html):
  • ```java
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.123456789");
  • System.out.println(dt); // 2021-10-01T10:30:45.123456789
  • ```
  • As the input string is in [ISO 8601 format](https://en.wikipedia.org/wiki/ISO_8601), it can be parsed directly. But you can also use a [`java.time.format.DateTimeFormatter`](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html):
  • ```java
  • DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS");
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.123456789", parser);
  • ```
  • The difference is that this `DateTimeFormatter` accepts **exactly** 9 decimal digits, while the one-arg `parse` method above uses a built-in parser that accepts from 0 to 9 digits:
  • ```java
  • // OK, it accepts from 0 to 9 decimal digits
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.12345");
  • // error, it accepts exactly 9 decimal digits
  • DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS");
  • dt = LocalDateTime.parse("2021-10-01T10:30:45.12345", parser); // DateTimeParseException
  • ```
  • Anyway, if the input string is in ISO 8601 format, there's no need to build a custom `DateTimeFormatter` (unless you need to be strict about the exact number of decimal digits).
  • But what if I need a different format (let's say, "day/month/year"), and the flexibility of a variable number of decimal digits?
  • In that case, you can use a `java.time.format.DateTimeFormatterBuilder`:
  • ```java
  • DateTimeFormatter parser = new DateTimeFormatterBuilder()
  • // let's say I need a different date format
  • .appendPattern("dd/MM/uuuu HH:mm:ss")
  • // fraction of second, from 0 to 9 digits and the decimal separator
  • .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
  • .toFormatter();
  • LocalDateTime dt = LocalDateTime.parse("01/10/2021 10:30:45.12345", parser); // DateTimeParseException
  • // output uses toString(), which always shows in ISO 8601
  • System.out.println(dt); // 2021-10-01T10:30:45.123450
  • // using the same DateTimeFormatter, it uses the specific format
  • System.out.println(dt.format(parser)); // 01/10/2021 10:30:45.12345
  • ```
  • Showing all the parsing/formatting options is beyond the scope of the question, so please [refer to the documentation](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html) to see all the possibilities. The point here is: if you need to handle up to 9 decimal digits in the fraction of seconds, `java.time` is the best choice, IMO.
  • Depending on the input, different types might be needed, as the API provides lots of options (such as `LocalTime` for time-only data, `ZonedDateTime` if you need to deal with timezones, etc). All of them can handle up to 9 decimal digits. And if you "need" a `Date` or `Calendar` instance (maybe because you're using legacy code that works only with those classes), [there are ways to convert to/from them](https://docs.oracle.com/javase/tutorial/datetime/iso/legacy.html).
  • ---
  • # JDK <= 7
  • If you're using JDK <= 7 (or is using a newer Java version, but has legacy code that still uses the old API), you can't use `SimpleDateFormat` directly like you did, because it'll return incorrect dates.
  • As previously said, the old date/time API (`Date`, `Calendar`, `SimpleDateFormat`) has millisecond precision and can't handle more than 3 decimal digits in the fraction of seconds.
  • ### But `SimpleDateFormat` didn't give any errors...
  • Yes, `SimpleDateFormat` is known to be **very** lenient and accept almost everything as input, and "tries its best" to give you a `Date`, even if it leads to wrong results (you can see some examples [here](https://eyalsch.wordpress.com/2009/05/29/sdf/) and [here](https://www.javaspecialists.eu/archive/Issue172-Wonky-Dating.html)).
  • In this specific case, the `S` token used in `SimpleDateFormat` pattern means "milliseconds" (while in `java.time` API, it means "fraction of seconds" - with nanosecond precision: same letter, different meaning, that's why it works in one API while failing in the other).
  • Therefore, when `SSSSSSSSS` is used with `SimpleDateFormat`, it means that 9 digits will be parsed and set to the milliseconds value. So in the string `2021-10-01T10:30:45.123456789`, the parser reads it as "123456789 milliseconds", which in turn is equivalent to "34 hours, 17 minutes, 36 seconds and 789 milliseconds". And `SimpleDateFormat` **adds** this to the date/time values previously read. If that makes sense or not, it's a discussion for another day[^1]. But that's what happened, and it explains why the resulting `Date` has some values that are completely different from the input.
  • [^1]: IMO, it doesn't :-)
  • If you need to store all the decimal digits, you'll have to store them separately from the date. The best you can do is to store the whole fraction of seconds in another field/variable, and optionally keep the `Date` with the milliseconds (the first 3 decimal digits). Something like this:
  • ```java
  • String s = "2021-10-01T10:30:45.12345";
  • // parse the date/time without the decimal digits
  • String[] parts = s.split("\\.");
  • SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
  • Date date = sdf.parse(parts[0]);
  • // check if there are decimal digits, store the value as nanoseconds
  • int nanos;
  • if (parts.length == 1) { // no decimal digits
  • nanos = 0;
  • } else {
  • // right-pad with zeroes, so the value is in nanoseconds
  • nanos = Integer.parseInt(String.format("%-9s", parts[1]).replaceAll(" ", "0"));
  • // The Date has no milliseconds, but we can add it
  • date.setTime(date.getTime() + (nanos / 1000000));
  • }
  • System.out.println(sdf.format(date)); // 2021-10-01T10:30:45
  • System.out.println(nanos); // 123450000
  • ```
  • You could also validate if `parts[1].length` is greater than 9 (and show an error message in that case, if the maximum precision you want to handle is 9 digits, of course), etc. But the general ideia is to store the fraction of seconds in a separate field, in case you don't want to lose precision.
  • If you don't mind losing precision and want to preserve just the first 3 decimal digits, you can use `date.setTime(date.getTime() + (nanos / 1000000))` as shown above, and then discard the `nanos` variable. This is needed because we used a pattern without milliseconds, so `SimpleDateFormat` sets its value to zero.
  • ### Alternative for JDK 6 and 7
  • If you're using JDK 6 or 7, it's possible to use the [Three-Ten Backport](https://www.threeten.org/threetenbp/), which provides a backport of the `java.time` API.
  • It's very similar to `java.time`, the difference is that all classes are in the `org.threeten.bp` package (the _names_ of the classes and their methods are the same, though, and all have nanosecond precision). One exception is the conversion to/from `Date`/`Calendar`: in Java >= 8 it's implemented as methods of those classes, but in the backport this is made by an [utilitary class](https://www.threeten.org/threetenbp/apidocs/org/threeten/bp/DateTimeUtils.html).
  • With the backport, the code is basically the same as above, with `DateTimeFormatter`, `LocalDateTime`, etc.
  • The solution depends on the Java version you're using. First, let's see the solution for earlier versions, that doesn't use `SimpleDateFormat`. Then we'll see why the problem happens and alternatives for older versions.
  • ---
  • # JDK >= 8
  • For JDK >= 8, you can (_should/must?_) use the [`java.time` API](https://docs.oracle.com/javase/tutorial/datetime/index.html). It supplants the old legacy API (`Date`, `Calendar`, `SimpleDateFormat`) and should be preferred for newer projects (unless you're stuck with the legacy for any reason).
  • The old API has milissecond precision and can't correctly handle more than 3 decimal digits. The new API has nanosecond precision and can handle up to 9 decimal digits without any problem.
  • Another difference is that in `java.time` there are [lots of different types](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html#h1) to represent dates and times. In your case, the input string has date and time, so the best fit is probably a [`java.time.LocalDateTime`](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html):
  • ```java
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.123456789");
  • System.out.println(dt); // 2021-10-01T10:30:45.123456789
  • ```
  • As the input string is in [ISO 8601 format](https://en.wikipedia.org/wiki/ISO_8601), it can be parsed directly. But you can also use a [`java.time.format.DateTimeFormatter`](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html):
  • ```java
  • DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS");
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.123456789", parser);
  • ```
  • The difference is that this `DateTimeFormatter` accepts **exactly** 9 decimal digits, while the one-arg `parse` method above uses a built-in parser that accepts from 0 to 9 digits:
  • ```java
  • // OK, it accepts from 0 to 9 decimal digits
  • LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.12345");
  • // error, it accepts exactly 9 decimal digits
  • DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS");
  • dt = LocalDateTime.parse("2021-10-01T10:30:45.12345", parser); // DateTimeParseException
  • ```
  • Anyway, if the input string is in ISO 8601 format, there's no need to build a custom `DateTimeFormatter` (unless you need to be strict about the exact number of decimal digits).
  • But what if I need a different format (let's say, "day/month/year"), and the flexibility of a variable number of decimal digits?
  • In that case, you can use a `java.time.format.DateTimeFormatterBuilder`:
  • ```java
  • DateTimeFormatter parser = new DateTimeFormatterBuilder()
  • // let's say I need a different date format
  • .appendPattern("dd/MM/uuuu HH:mm:ss")
  • // fraction of second, from 0 to 9 digits and the decimal separator
  • .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
  • .toFormatter();
  • LocalDateTime dt = LocalDateTime.parse("01/10/2021 10:30:45.12345", parser); // DateTimeParseException
  • // output uses toString(), which always shows in ISO 8601
  • System.out.println(dt); // 2021-10-01T10:30:45.123450
  • // using the same DateTimeFormatter, it uses the specific format
  • System.out.println(dt.format(parser)); // 01/10/2021 10:30:45.12345
  • ```
  • Showing all the parsing/formatting options is beyond the scope of the question, so please [refer to the documentation](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html) to see all the possibilities. The point here is: if you need to handle up to 9 decimal digits in the fraction of seconds, `java.time` is the best choice, IMO.
  • Depending on the input, different types might be needed, as the API provides lots of options (such as `LocalTime` for time-only data, `ZonedDateTime` if you need to deal with timezones, etc). All of them can handle up to 9 decimal digits. And if you "need" a `Date` or `Calendar` instance (maybe because you're using legacy code that works only with those classes), [there are ways to convert to/from them](https://docs.oracle.com/javase/tutorial/datetime/iso/legacy.html).
  • ---
  • # JDK <= 7
  • If you're using JDK <= 7 (or is using a newer Java version, but has legacy code that still uses the old API), you can't use `SimpleDateFormat` directly like you did, because it'll return incorrect dates.
  • As previously said, the old date/time API (`Date`, `Calendar`, `SimpleDateFormat`) has millisecond precision and can't handle more than 3 decimal digits in the fraction of seconds.
  • ### But `SimpleDateFormat` didn't give any errors...
  • Yes, `SimpleDateFormat` is known to be **very** lenient and accept almost everything as input, and "tries its best" to give you a `Date`, even if it leads to wrong results (you can see some examples [here](https://eyalsch.wordpress.com/2009/05/29/sdf/) and [here](https://www.javaspecialists.eu/archive/Issue172-Wonky-Dating.html)).
  • In this specific case, the `S` token used in `SimpleDateFormat` pattern means "milliseconds" (while in `java.time` API, it means "fraction of seconds" - with nanosecond precision: same letter, different meaning, that's why it works in one API while failing in the other).
  • Therefore, when `SSSSSSSSS` is used with `SimpleDateFormat`, it means that 9 digits will be parsed and set to the milliseconds value. So in the string `2021-10-01T10:30:45.123456789`, the parser reads it as "123456789 milliseconds", which in turn is equivalent to "34 hours, 17 minutes, 36 seconds and 789 milliseconds". And `SimpleDateFormat` **adds** this to the date/time values previously read. If that makes sense or not, it's a discussion for another day[^1]. But that's what happened, and it explains why the resulting `Date` has some values that are completely different from the input.
  • If you need to store all the decimal digits, you'll have to store them separately from the date. The best you can do is to store the whole fraction of seconds in another field/variable, and optionally keep the `Date` with the milliseconds (the first 3 decimal digits). Something like this:
  • ```java
  • String s = "2021-10-01T10:30:45.12345";
  • // parse the date/time without the decimal digits
  • String[] parts = s.split("\\.");
  • SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
  • Date date = sdf.parse(parts[0]);
  • // check if there are decimal digits, store the value as nanoseconds
  • int nanos;
  • if (parts.length == 1) { // no decimal digits
  • nanos = 0;
  • } else {
  • // right-pad with zeroes, so the value is in nanoseconds
  • nanos = Integer.parseInt(String.format("%-9s", parts[1]).replaceAll(" ", "0"));
  • // The Date has no milliseconds, but we can add it
  • date.setTime(date.getTime() + (nanos / 1000000));
  • }
  • System.out.println(sdf.format(date)); // 2021-10-01T10:30:45
  • System.out.println(nanos); // 123450000
  • ```
  • You could also validate if `parts[1].length` is greater than 9 (and show an error message in that case, if the maximum precision you want to handle is 9 digits, of course), etc. But the general ideia is to store the fraction of seconds in a separate field, in case you don't want to lose precision.
  • If you don't mind losing precision and want to preserve just the first 3 decimal digits, you can use `date.setTime(date.getTime() + (nanos / 1000000))` as shown above, and then discard the `nanos` variable. This is needed because we used a pattern without milliseconds, so `SimpleDateFormat` sets its value to zero.
  • ### Alternative for JDK 6 and 7
  • If you're using JDK 6 or 7, it's possible to use the [Three-Ten Backport](https://www.threeten.org/threetenbp/), which provides a backport of the `java.time` API.
  • It's very similar to `java.time`, the difference is that all classes are in the `org.threeten.bp` package (the _names_ of the classes and their methods are the same, though, and all have nanosecond precision). One exception is the conversion to/from `Date`/`Calendar`: in Java >= 8 it's implemented as methods of those classes, but in the backport this is made by an [utilitary class](https://www.threeten.org/threetenbp/apidocs/org/threeten/bp/DateTimeUtils.html).
  • With the backport, the code is basically the same as above, with `DateTimeFormatter`, `LocalDateTime`, etc.
  • [^1]: IMO, it doesn't make any sense :-)
#1: Initial revision by user avatar hkotsubo‭ · 2021-08-20T16:24:19Z (over 2 years ago)
The solution depends on the Java version you're using. First, let's see the solution for earlier versions, that doesn't use `SimpleDateFormat`. Then we'll see why the problem happens and alternatives for older versions.

---
# JDK >= 8

For JDK >= 8, you can (_should/must?_) use the [`java.time` API](https://docs.oracle.com/javase/tutorial/datetime/index.html). It supplants the old legacy API (`Date`, `Calendar`, `SimpleDateFormat`) and should be preferred for newer projects (unless you're stuck with the legacy for any reason).

The old API has milissecond precision and can't correctly handle more than 3 decimal digits. The new API has nanosecond precision and can handle up to 9 decimal digits without any problem.

Another difference is that in `java.time` there are [lots of different types](https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html#h1) to represent dates and times. In your case, the input string has date and time, so the best fit is probably a [`java.time.LocalDateTime`](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html):

```java
LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.123456789");
System.out.println(dt); // 2021-10-01T10:30:45.123456789
```

As the input string is in [ISO 8601 format](https://en.wikipedia.org/wiki/ISO_8601), it can be parsed directly. But you can also use a [`java.time.format.DateTimeFormatter`](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html):

```java
DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS");
LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.123456789", parser);
```

The difference is that this `DateTimeFormatter` accepts **exactly** 9 decimal digits, while the one-arg `parse` method above uses a built-in parser that accepts from 0 to 9 digits:

```java
// OK, it accepts from 0 to 9 decimal digits
LocalDateTime dt = LocalDateTime.parse("2021-10-01T10:30:45.12345");

// error, it accepts exactly 9 decimal digits
DateTimeFormatter parser = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSSSSSSS");
dt = LocalDateTime.parse("2021-10-01T10:30:45.12345", parser); // DateTimeParseException
```

Anyway, if the input string is in ISO 8601 format, there's no need to build a custom `DateTimeFormatter` (unless you need to be strict about the exact number of decimal digits).

But what if I need a different format (let's say, "day/month/year"), and the flexibility of a variable number of decimal digits?
In that case, you can use a `java.time.format.DateTimeFormatterBuilder`:

```java
DateTimeFormatter parser = new DateTimeFormatterBuilder()
    // let's say I need a different date format
    .appendPattern("dd/MM/uuuu HH:mm:ss")
    // fraction of second, from 0 to 9 digits and the decimal separator
    .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
    .toFormatter();
LocalDateTime dt = LocalDateTime.parse("01/10/2021 10:30:45.12345", parser); // DateTimeParseException

// output uses toString(), which always shows in ISO 8601
System.out.println(dt); // 2021-10-01T10:30:45.123450
// using the same DateTimeFormatter, it uses the specific format
System.out.println(dt.format(parser)); // 01/10/2021 10:30:45.12345
```

Showing all the parsing/formatting options is beyond the scope of the question, so please [refer to the documentation](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html) to see all the possibilities. The point here is: if you need to handle up to 9 decimal digits in the fraction of seconds, `java.time` is the best choice, IMO.

Depending on the input, different types might be needed, as the API provides lots of options (such as `LocalTime` for time-only data, `ZonedDateTime` if you need to deal with timezones, etc). All of them can handle up to 9 decimal digits. And if you "need" a `Date` or `Calendar` instance (maybe because you're using legacy code that works only with those classes), [there are ways to convert to/from them](https://docs.oracle.com/javase/tutorial/datetime/iso/legacy.html).

---
# JDK <= 7

If you're using JDK <= 7 (or is using a newer Java version, but has legacy code that still uses the old API), you can't use `SimpleDateFormat` directly like you did, because it'll return incorrect dates.

As previously said, the old date/time API (`Date`, `Calendar`, `SimpleDateFormat`) has millisecond precision and can't handle more than 3 decimal digits in the fraction of seconds.

### But `SimpleDateFormat` didn't give any errors...

Yes, `SimpleDateFormat` is known to be **very** lenient and accept almost everything as input, and "tries its best" to give you a `Date`, even if it leads to wrong results (you can see some examples [here](https://eyalsch.wordpress.com/2009/05/29/sdf/) and [here](https://www.javaspecialists.eu/archive/Issue172-Wonky-Dating.html)).

In this specific case, the `S` token used in `SimpleDateFormat` pattern means "milliseconds" (while in `java.time` API, it means "fraction of seconds" - with nanosecond precision: same letter, different meaning, that's why it works in one API while failing in the other).

Therefore, when `SSSSSSSSS` is used with `SimpleDateFormat`, it means that 9 digits will be parsed and set to the milliseconds value. So in the string `2021-10-01T10:30:45.123456789`, the parser reads it as "123456789 milliseconds", which in turn is equivalent to "34 hours, 17 minutes, 36 seconds and 789 milliseconds". And `SimpleDateFormat` **adds** this to the date/time values previously read. If that makes sense or not, it's a discussion for another day[^1]. But that's what happened, and it explains why the resulting `Date` has some values that are completely different from the input.

  [^1]: IMO, it doesn't :-)

If you need to store all the decimal digits, you'll have to store them separately from the date. The best you can do is to store the whole fraction of seconds in another field/variable, and optionally keep the `Date` with the milliseconds (the first 3 decimal digits). Something like this:

```java
String s = "2021-10-01T10:30:45.12345";
// parse the date/time without the decimal digits
String[] parts = s.split("\\.");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
Date date = sdf.parse(parts[0]);

// check if there are decimal digits, store the value as nanoseconds
int nanos;
if (parts.length == 1) { // no decimal digits
    nanos = 0;
} else {
    // right-pad with zeroes, so the value is in nanoseconds
    nanos = Integer.parseInt(String.format("%-9s", parts[1]).replaceAll(" ", "0"));
    // The Date has no milliseconds, but we can add it
    date.setTime(date.getTime() + (nanos / 1000000));
}
System.out.println(sdf.format(date)); // 2021-10-01T10:30:45
System.out.println(nanos); // 123450000
```

You could also validate if `parts[1].length` is greater than 9 (and show an error message in that case, if the maximum precision you want to handle is 9 digits, of course), etc. But the general ideia is to store the fraction of seconds in a separate field, in case you don't want to lose precision.

If you don't mind losing precision and want to preserve just the first 3 decimal digits, you can use `date.setTime(date.getTime() + (nanos / 1000000))` as shown above, and then discard the `nanos` variable. This is needed because we used a pattern without milliseconds, so `SimpleDateFormat` sets its value to zero.


### Alternative for JDK 6 and 7

If you're using JDK 6 or 7, it's possible to use the [Three-Ten Backport](https://www.threeten.org/threetenbp/), which provides a backport of the `java.time` API.

It's very similar to `java.time`, the difference is that all classes are in the `org.threeten.bp` package (the _names_ of the classes and their methods are the same, though, and all have nanosecond precision). One exception is the conversion to/from `Date`/`Calendar`: in Java >= 8 it's implemented as methods of those classes, but in the backport this is made by an [utilitary class](https://www.threeten.org/threetenbp/apidocs/org/threeten/bp/DateTimeUtils.html).

With the backport, the code is basically the same as above, with `DateTimeFormatter`, `LocalDateTime`, etc.