2

According to http://apiux.com/2013/03/20/5-laws-api-dates-and-times/ we should use ISO 8601 to format date. The output on my system is:

$ date --iso-8601=ns
2020-10-29T10:38:59,112768965+01:00

Which java formatter parse this string correctly? I've tried DateTimeFormatter.ISO_OFFSET_DATE_TIME and a few others, and they don't like this format. I expect that such a format should be supported out-of-the-box, as Oracle claims on its page https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html but it isn't.

Another thing is that Spring announced wide support for date format: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes#date-time-conversion-in-web-applications. I can then set all properties to "iso". But which exactly format is it, and where is it in use?

I see two problems here.

I would like to have consistent approach to date format across whole application, and I would like to use standard to easily integrate with the rest of the world. What should I use?

Edit: In the end it happened that there are a few things to consider:

  • Example from Linux console was artificial, as I didn't notice this format with "," in real world. It turned out, that it's slightly inconsistently supported in Java world
  • ObjectMapper, ModelMapper and OffsetDateTime makes Date object conversion different, especially conversion to ISO format ('Z' or '00:00')
  • Offset definition in form of "0000" also existed in Java, and I had to fix my conversion
Marx
  • 804
  • 10
  • 23
  • ISO 8601 is the default format string for all the `java.time` classes. The problem seems to be that little comma is supposed to be a dot. – coladict Oct 29 '20 at 10:04
  • Some related questions: https://stackoverflow.com/questions/30135025 and https://stackoverflow.com/questions/31477309 – jschnasse Oct 29 '20 at 10:16
  • 1
    Reading-up on what is publicly available about ISO8601, it says either comma or full stop can be used, but "comma is to be preferred", however Java strictly uses full stop by default. The parser does not support automatic floating point style detection. – coladict Oct 29 '20 at 10:19

3 Answers3

3

ISO 8601-1 allows both , and . as decimal separators, though in practice this is the first time I've seen , actually in use. The default formats in JDK 11 only support parsing . (even though the documentation says the decimal separator is localised). To handle this format you have some options:

  1. Modify the string before parsing:
OffsetDateTime.parse(value.replaceAll(",", "."));
  1. Define a custom format:
 new DateTimeFormatterBuilder()
         .parseCaseInsensitive()
         .append(ISO_LOCAL_DATE)
         .appendLiteral('T')
         .appendValue(HOUR_OF_DAY, 2)
         .appendLiteral(':')
         .appendValue(MINUTE_OF_HOUR, 2)
         .optionalStart()
         .appendLiteral(':')
         .appendValue(SECOND_OF_MINUTE, 2)
         .optionalStart()
         .appendLiteral(',')
         .appendFraction(NANO_OF_SECOND, 0, 9, false)
         .parseLenient()
         .appendOffsetId()
         .toFormatter()
         .parse(value)
  1. Use a locale that uses , separators (confirmed working in JDK 15):
DateTimeFormatter.ISO_OFFSET_DATE_TIME.localizedBy(Locale.FRANCE).parse(value);

There does not seem to be a way to define a format that allows either , or . but not both. You'd have to either try one catch the exception and try the other, or detect which format it's in (e.g. value.contains(",")) and use the appropriate formatter.

OrangeDog
  • 36,653
  • 12
  • 122
  • 207
  • Localizing the built-in formatter, I wouldn’t have dreamt of that being possible. Thanks very much for the demonstration! – Ole V.V. Oct 29 '20 at 15:58
  • @OleV.V. I couldn't find a bug ticket for it, or I'd know which version it was fixed in – OrangeDog Oct 29 '20 at 16:23
2

If your string always has got 9 decimals after the comma, just specify a comma in the format pattern string:

    DateTimeFormatter isoWithCommaFormatter = DateTimeFormatter
            .ofPattern("uuuu-MM-dd'T'HH:mm:ss,SSSSSSSSSXXX");
    String iso = "2020-10-29T10:38:59,112768965+01:00";
    OffsetDateTime odt = OffsetDateTime.parse(iso, isoWithCommaFormatter);
    System.out.println(odt);

Output:

2020-10-29T10:38:59.112768965+01:00

If the number of decimals may vary, you need a DateTimeFormatterBuilder, but the basic trick is the same simple one: put a comma in the format pattern string.

    DateTimeFormatter isoWithCommaFormatter = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ISO_LOCAL_DATE)
            .appendLiteral('T')
            .appendPattern("HH:mm:ss[,")
            .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, false)
            .appendPattern("]XXX")
            .toFormatter();

The ISO 8601 format recommends comma over dot as decimal separator, so it’s a little bit funny that the built in formatters and the one-arg parse methods only accept dot.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • `If your string always has got 9 decimals after the comma...` - why to put such a restriction when one can simply use a single `n` for all allowed number of digits for nanoseconds? – Arvind Kumar Avinash Oct 30 '20 at 14:31
  • @ArvindKumarAvinash because `23,456` means the same as `23,456000000`, but parsing it with a single `n` would interpret it as `23,000000456`. – Ole V.V. Oct 30 '20 at 14:33
  • Sorry, I didn't understand. How can nanoseconds be fractional i.e. `23,456`? A string like `2020-10-29T10:38:23,456+01:00` will be correctly parsed as `2020-10-29T10:38:23.000000456+01:00` using a single `n`. My upvote will still remain for the second part of your answer. – Arvind Kumar Avinash Oct 30 '20 at 14:59
  • @ArvindKumarAvinash `2020-10-29T10:38:23,456+01:00` should be parsed as 2020-10-29T10:38:23.456+01:00. Parsing it into 2020-10-29T10:38:23.000000456+01:00 is incorrect, it’s 0.455999544 second too early. Because the `456` in `23,456` are not nanoseconds, they are fraction of second. 4 10ths, 5 100ths and 6 1000ths of a second. – Ole V.V. Oct 30 '20 at 15:04
  • It’s how decimal numbers are. Just like 1.01 and 1.1 aren’t equal either. This statement hold true no matter if you write those numbers with a dot or a comma. You may say that the 456 in 23,456 are milliseoncds since they are exactly three digits. But since a millisecond and a nanosecond are not the same, they cannot at the same time be nanoseconds. – Ole V.V. Oct 30 '20 at 17:24
2

You can make the . or , as an optional pattern by putting them in a square bracket.

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        DateTimeFormatter isoFormatter = new DateTimeFormatterBuilder()
                .appendPattern("u-M-d'T'H:m:s[.][,]")
                .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, false)
                .appendPattern("[XXX][XX][X]")
                .toFormatter(Locale.ENGLISH);

        // Test
        System.out.println(OffsetDateTime.parse("2021-05-21T10:20:30,123456789+02:00", isoFormatter));
        System.out.println(OffsetDateTime.parse("2021-05-21T10:20:30,123456789+0200", isoFormatter));
        System.out.println(OffsetDateTime.parse("2021-05-21T10:20:30,123456789+02", isoFormatter));
        System.out.println(OffsetDateTime.parse("2021-05-21T10:20:30.123456789+02:00", isoFormatter));
        System.out.println(OffsetDateTime.parse("2021-05-21T10:20:30+02:00", isoFormatter));
    }
}

Output:

2021-05-21T10:20:30.123456789+02:00
2021-05-21T10:20:30.123456789+02:00
2021-05-21T10:20:30.123456789+02:00
2021-05-21T10:20:30.123456789+02:00
2021-05-21T10:20:30+02:00

Alternatively,

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

public class Main {
    public static void main(String[] args) {
        DateTimeFormatter isoFormatter = DateTimeFormatter.ofPattern(
                "u[-M[-d]]['T'H[:m[:s[.][,][SSSSSSSSS][SSSSSSSS][SSSSSSS][SSSSSS][SSSSS][SSSS][SSS][SS][S]]]][XXX][XX][X]",
                Locale.ENGLISH);

        // Test
        System.out.println(OffsetDateTime.parse("2021-05-21T10:20:30,123456789+02:00", isoFormatter));
        System.out.println(OffsetDateTime.parse("2021-05-21T10:20:30,123456789+0200", isoFormatter));
        System.out.println(OffsetDateTime.parse("2021-05-21T10:20:30,123456789+02", isoFormatter));
        System.out.println(OffsetDateTime.parse("2021-05-21T10:20:30.123456789+02:00", isoFormatter));
        System.out.println(OffsetDateTime.parse("2021-05-21T10:20:30+02:00", isoFormatter));
    }
}

The output is the same.

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110