Why is Instant.now() sometimes formatted differently?
Asked Answered
C

4

10

I have code that looks like this:

String PROPER_DATE_FORMAT = "yyyy-MM-dd";
String format = "yyyy-MM-dd'T'HH:mm:ss.SSSX";

SimpleDateFormat sdf = new SimpleDateFormat(format);
Date d = sdf.parse(Instant.now().toString());
SimpleDateFormat properFormat = new SimpleDateFormat(PROPER_DATE_FORMAT);
String formatterDate = properFormat.format(d);

Now, most of the time, this code works but every now and then, I'll Instant.now() returning 2018-05-25T18:56:09Z and then it will throw an exception bc the format doesn't match.

Why is it sometimes inconsistent?

The exception is java.lang.RuntimeException: java.text.ParseException: Unparseable date: "2018-05-25T18:56:09Z"

Normally, when Instant.now().toString() does return a String with the above format. Again, this code works most of the time but there are a few times when it doesn't and I'm puzzled as to why.

I thought about whether it was because there's a zero at the end but just wasn't displayed but I'm not convinced because I was able to get a value of 2018-05-25T20:06:58.900Z as a result of Instant.now().toString()

Clemenciaclemency answered 25/5, 2018 at 19:52 Comment(15)
Thanks, I have included what the exception wasClemenciaclemency
What's the value of PROPER_DATE_FORMAT?Vitek
I updated the question with the value.Clemenciaclemency
perhaps sometimes the Instance.now().toString() call returns a value with only two digits in the ms (while the format expects three digits) ?Gall
@Vitek the exception is thrown before that line (while parsing) so the PROPER_DATE_FORMAT does not seem to be relevant in this caseGall
No, whenever I print out the value of Instant.now().toString(), it returns a value with 3 digits, like this: 2018-05-25T20:06:58.905ZClemenciaclemency
I thought about whether it was bc there's a zero at the end but just wasn't displayed but I'm not convinced bc I was able to get a value of 2018-05-25T20:06:58.900Z as a result of Instant.now().toString()Clemenciaclemency
Why do you convert the instant to a string only to parse it into a date? Why not write Date d = new Date(Instant.now().toEpochMilli());Wagram
In this case, yes - but try parsing 2018-05-25T20:06:58.90Z with the same formatterGall
Parsing 2018-05-25T20:06:58.90Z with the same formatter will return the exception above as it should.Clemenciaclemency
so, perhaps (once in a while) Instance.now().toString() returns only 1 or 2 digits in the ms (and not 3) as expected?Gall
Instant.toString() outputs digits for millis only if necessary (if they are not 000). So you have a one in thousand chance of hitting exactly the second (with all milliseconds digits zero) where your code will failWagram
Since you can use java.time, the modern Java date and time API, don’t use Date too, it’s long outdated and poorly designed. Only if you need a Date for a legacy API that you cannot change. Only in this case, use Date.from(Instant) for conversion. And under no circumstances at all use the notoriously troublesome SimpleDateFormat.Polyurethane
To obtain your string in yyyy-MM-dd format, you can use LocalDate.now(yourZoneId).toString().Polyurethane
As an aside, on the Java 9 on my computer, Instant.now().toString() yields something like 2018-05-26T05:56:50.294304Z, which your code converts to a Date of Sat May 26 08:01:44 CEST 2018. CEST is at offset +02:00, so you notice the time is not correct.Polyurethane
V
9

When the nanoseconds is zero, it's not included in the formatted string. So the output would be formatted as "yyyy-MM-dd'T'HH:mm:ssX"

You can see the difference with:

public static void main(String[] args) throws ParseException {
        final Instant now = Instant.now();
        System.out.println("Now:\t" +  now);

        final Instant zeroedNow = now.with(ChronoField.NANO_OF_SECOND, 0);
        System.out.println("Zeroed:\t" + zeroedNow);
} 

The output looks like

Now:    2018-05-25T20:23:54.208Z
Zeroed: 2018-05-25T20:23:54Z
Vitek answered 25/5, 2018 at 20:16 Comment(0)
K
9

Your code fails because Instant.toString() is documented as

The format used is the same as DateTimeFormatter.ISO_INSTANT.

And according to DateTimeFormatter.html#ISO_INSTANT

The nano-of-second outputs zero, three, six or nine digits digits as necessary

So you have a one in thousand chance of getting an Instant where the millis are 000 and your code fails.


Anyway, your code is unnecessarly complex and could be replaced with

String formattedDate = DateTimeFormatter.ofPattern("yyyy-MM-dd")
    .withZone(ZoneId.systemDefault())
    .format(Instant.now());

There is no need to transform the Instant into a String only to parse it into a Date and then transform it into a String again.

Kenay answered 25/5, 2018 at 20:18 Comment(2)
That is not what was asked.Vitek
I agree with @Vitek — while yours is (probably) a better way to go about doing this conversion, it doesn't answer the question of "Why is Instant.now() sometimes formatted differently?"Newsmonger
R
1

To answer your question: "Instant.now() sometimes formatted differently?". Lets take a look at how Instance.now().toString() was implemented:

Instant.now().toString() source:

public String toString() {
    return DateTimeFormatter.ISO_INSTANT.format(this);
}

Javadoc of

DateTimeFormatter.ISO_INSTANT

https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_INSTANT

 When formatting, the second-of-minute is always output.
 The nano-of-second outputs zero, three, six or nine digits digits as necessary.

From this javadoc, we understand that Instance.now.toString() NOT always 24 length.

If milliseconds is 000 -> It will produce the format like you got:

2018-05-25T18:56:09Z - 20 length

NOT

2018-05-25T18:56:09.000Z - 24 length

Is everything clear?

Rici answered 25/5, 2018 at 20:56 Comment(0)
G
0

You can format Instant using

instant.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"))

if you want to use the same api and in absence of any standard approach you can think of putting following logic

Instant inst = Instant.now()
if(inst.get(ChronoField.MILLI_OF_SECOND) == 0)
inst  = inst.minusMillis(1)

Gandzha answered 1/2, 2023 at 8:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.