3

I have several code snippets. Some of them works and some not but I don't understand why.

DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'");  //(7 positions after last dor)

TIME_FORMATTER.parse("2021-06-22T18:27:03.5577Z")//Broken 4 
TIME_FORMATTER.parse("2021-06-22T18:27:03.55770Z")//Broken 5
TIME_FORMATTER.parse("2021-06-22T18:27:03.557700Z")//Working 6 
TIME_FORMATTER.parse("2021-06-22T18:27:03.5577000Z")//Working 7 
TIME_FORMATTER.parse("2021-06-22T18:27:03.55770000Z")//Broken 8

See this code work, or not work, when running live at IdeOne.com.

Why it works for both: 6 and 7 digits after the decimal separator, but not 4, 5, or 8 digits?

How to create a formatter which would work for 4, 5, 6, 7, or 8 digits?

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
gstackoverflow
  • 36,709
  • 117
  • 359
  • 710

4 Answers4

2

You can use optional formats in the pattern to parse the expected formats. For your specific case, the following works.

DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.[SSSSSSSS][SSSSSSS][SSSSS][SSSS]'Z'");

It is not very clear in the documentation, but the order of the optional sections matter.

DateTimeFormatter documentation

Also, this form of parsing has performance impacts and the usage of DateTimeFormatterBuilder is advised.

aksappy
  • 3,400
  • 3
  • 23
  • 49
2

tl;dr

You asked:

How to create Format which will work for 4,5,6,7 numbers after point ?

Use the predefined formatter, DateTimeFormatter.ISO_INSTANT.

DateTimeFormatter.ISO_INSTANT.parse("2021-06-22T18:27:03.5577Z")

Never ignore Z

Never put quote-marks around Z in your formatting pattern. That letter is carries crucial information, and is not mere decoration. That letter indicates an offset from UTC of zero hours-minutes-seconds. Your single-quote-marks around Z indicate that letter should be expected and then ignored.

When ignoring the offset, you are left with a date and time only. A date and time are not enough to represent a moment. We cannot know if your input meant 6:30 PM in Tokyo, 6:30 PM in Toulouse, or 6:30 PM in Toledo — all very different moments several hours apart.

For a point on the timeline we require a third piece, the context of an offset or a time zone.

java.time.Instant.parse

Your input text complies with the ISO 8601 standard used by default in java.time. So no need to specify a formatting pattern.

Simply parse the input as an Instant object. The Instant represents a moment as seen in UTC, with an offset of zero.

The Instant.parse method uses the predefined formatter in the constant DateTimeFormatter.ISO_INSTANT.

Instant instant4 = Instant.parse("2021-06-22T18:27:03.5577Z") ;
Instant instant5 = Instant.parse("2021-06-22T18:27:03.55770Z") ;
Instant instant6 = Instant.parse("2021-06-22T18:27:03.557700Z") ;
Instant instant7 = Instant.parse("2021-06-22T18:27:03.5577000Z") ;
Instant instant8 = Instant.parse("2021-06-22T18:27:03.55770000Z") ;

See this code run live at IdeOne.com.

2021-06-22T18:27:03.557700Z
2021-06-22T18:27:03.557700Z
2021-06-22T18:27:03.557700Z
2021-06-22T18:27:03.557700Z

If curious as to how ISO_INSTANT is written in OpenJDK, see the source code.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
0

As Basil says, you should probably use DateTimeFormatter.ISO_INSTANT as your format, which results in all successes and more accurate results:

Success: 2021-06-22T18:27:03.5577Z      {InstantSeconds=1624386423, MilliOfSecond=557, MicroOfSecond=557700, NanoOfSecond=557700000},ISO
Success: 2021-06-22T18:27:03.55770Z     {InstantSeconds=1624386423, MilliOfSecond=557, MicroOfSecond=557700, NanoOfSecond=557700000},ISO
Success: 2021-06-22T18:27:03.557700Z        {InstantSeconds=1624386423, MilliOfSecond=557, MicroOfSecond=557700, NanoOfSecond=557700000},ISO
Success: 2021-06-22T18:27:03.5577000Z       {InstantSeconds=1624386423, MilliOfSecond=557, MicroOfSecond=557700, NanoOfSecond=557700000},ISO
Success: 2021-06-22T18:27:03.55770000Z      {InstantSeconds=1624386423, MilliOfSecond=557, MicroOfSecond=557700, NanoOfSecond=557700000},ISO

The real question though is, "What is expected of this formatter"? It seems like it's meant to be parsing instants but without documentation we're not sure. Is it really meant to fail on certain date formats which are supported by instant, is it really meant to be expecting a literal 'Z' rather than the offset? If it's intent is to be able to parse these instants accurately then changing it to actually be accurate is what you should do.

BUT If it's meant to be more restrictive and fail in weird cases then using aksappy's idea might be the way to go, since his format keeps close to your existing format but allows you to explicitly add to the pattern to match new formats you want to match.


I expect that your success with six digits after the decimal is a bug in your java.time or whichever library is providing this API.

Running with OpenJDK 16.0.1:

public static void main(String[] args) {
    DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'");
    String[] dates = {
            "2021-06-22T18:27:03.5577Z",
            "2021-06-22T18:27:03.55770Z",
            "2021-06-22T18:27:03.557700Z",
            "2021-06-22T18:27:03.5577000Z", 
            "2021-06-22T18:27:03.55770000Z",
    };
    for (String date : dates) {
        try {
            System.out.println("Success: " + date + "\t\t" + TIME_FORMATTER.parse(date));
        } catch (Exception e) {
            System.out.println("Failure: " + date);
        }
    }
}

I get the following results that match what I expect based on your format:

Failure: 2021-06-22T18:27:03.5577Z      Text '2021-06-22T18:27:03.5577Z' could not be parsed at index 20
Failure: 2021-06-22T18:27:03.55770Z     Text '2021-06-22T18:27:03.55770Z' could not be parsed at index 20
Failure: 2021-06-22T18:27:03.557700Z        Text '2021-06-22T18:27:03.557700Z' could not be parsed at index 20
Success: 2021-06-22T18:27:03.5577000Z       {},ISO resolved to 2021-06-22T18:27:03.557700
Failure: 2021-06-22T18:27:03.55770000Z      Text '2021-06-22T18:27:03.55770000Z' could not be parsed at index 27
xtratic
  • 4,600
  • 2
  • 14
  • 32
-1

If you want to make sure that you pass date only in UTC timezone and only with 'Z' literal and if fail is expexcted result if timezone is different this code is working solution:

    new DateTimeFormatterBuilder()
        .append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"))
        .appendFraction(ChronoField.MICRO_OF_SECOND, 4, 7, true)
        .appendLiteral("Z")
        .toFormatter().parse("2021-06-22T18:27:03.5577Z");
gstackoverflow
  • 36,709
  • 117
  • 359
  • 710
  • You are incorrectly treating the `Z` as mere decoration. You are ignoring the crucial information it represents, the offset from UTC. You throw away vital info while gaining nothing in return. I must down-vote this inadequate solution. – Basil Bourque Jul 16 '21 at 16:14
  • @Basil Bourque how to fix it ? – gstackoverflow Jul 16 '21 at 16:18
  • 1
    ?? Use `DateTimeFormatter.ISO_INSTANT` as I showed in [my Answer](https://stackoverflow.com/a/68411888/642706). You seem determined to make this needlessly complicated, as indicated in your various comments. Perhaps you’ve not explained some aspect of your Question? – Basil Bourque Jul 16 '21 at 16:22
  • @Basil Bourque I saw your approach and upvoted it. I am curious how to achieve it using *DateTimeFormatterBuilder* – gstackoverflow Jul 16 '21 at 16:23
  • You already have a formatter built into Java, so use it. Why recreate it? If you are curious as to how that formatter was built, look at the OpenJDK source code. – Basil Bourque Jul 16 '21 at 16:25
  • @Basil Bourque I don't see any literal here and in the deep: `ISO_INSTANT = new DateTimeFormatterBuilder() .parseCaseInsensitive() .appendInstant() .toFormatter(ResolverStyle.STRICT, null);` – gstackoverflow Jul 16 '21 at 16:26
  • 1
    If you wish to explore that source code, post a fresh Question on that specific issue. – Basil Bourque Jul 16 '21 at 16:28
  • @Basil Bourque it has no sense I think. Here we discussing my answer, You say that I lost timezone in my solution. I asked you how to fix imy code to avoid it. You question is great and correct but I want to know how to correct my answer. – gstackoverflow Jul 16 '21 at 16:32
  • Specifically, your call `.appendLiteral("Z")` tells the formatter to expect but ignore the `Z` rather than parse it as a meaningful offset of zero. – Basil Bourque Jul 16 '21 at 18:13
  • @Basil Bourque could you please clarify what do you mean "ignore Z" ? – gstackoverflow Jul 16 '21 at 22:05
  • 1
    @gstackoverflow - It is pretty clear what Basil means. Look at the format string spec in the javadocs. Note that there are format characters that that will recognize a `Z` as a zone. He is suggesting that you should use one of those ... as appropriate ... so that you match the zone rather than treating the `Z` as an insignificant character to be thrown away (like the `T`) – Stephen C Jul 17 '21 at 11:37
  • @Stephen C What does it mean ignore? Would it be considered as a mashine timezone or whatever ? Would it be a problem if string will be passed as a UTC only ? – gstackoverflow Jul 19 '21 at 09:40
  • Yes it would (could) be a problem. Because you can't be sure that it will only ever be UTC? Someone might think: "Oh. That's a zone marker. The API supports zones. **I will use a different one!**". Or "I don't like `Z` ... I will use `+00:00` instead." – Stephen C Jul 19 '21 at 12:10
  • @Stephen This string is generated by another micro service and we have agreement in our services that we send dates only as UTC so it should not be an issue for us. Isn't it ? – gstackoverflow Jul 19 '21 at 12:24
  • If someone will break the rule and send +00:00 instead or smth else our service won't accept this request at all. – gstackoverflow Jul 19 '21 at 12:32
  • It is fine until someone misinterprets the agreement. Or the spec is mislaid. Why take the risk? But here's the thing, you are asking experts (like Basil) for their advice. If you not going to pay attention to their advice ... why did you ask? – Stephen C Jul 19 '21 at 12:32
  • @Stephen C From one side I agree with you, from another side -we can't handle all cases when others devs breaks agreeement. BTW. I just wanted to understand the borders of risks instead of blind trust that my service is broken because of Z literal. Thank you very much for your clarification. Based on this information I can make a correct decision for my case. – gstackoverflow Jul 19 '21 at 12:36
  • Also ... we don't actually care what >you< do in >your< system. But your answer here constitutes advice to >other< people who may not be able to assume that their system always gets a Z as the timezone. As such ... it is bad advice. – Stephen C Jul 19 '21 at 12:37
  • @Stephen C It is a good advice with some limitations I sould say. Good point - will add them. – gstackoverflow Jul 19 '21 at 14:05