java.time.DateTimeFormatter : Need ISO_INSTANT that always renders milliseconds

Florent picture Florent · Jun 26, 2016 · Viewed 12.9k times · Source

I'm trying to cleanup a mix of various code around datetime management to only Java 8 java.time namespace. Right now I have a small issue with the default DateTimeFormatter for Instant. The DateTimeFormatter.ISO_INSTANT formatter only shows milliseconds when they are not equal to zero.

The epoch is rendered as 1970-01-01T00:00:00Z instead of 1970-01-01T00:00:00.000Z.

I made a unit test to explain the problem and how we need to final dates to be compared one to each other.

@Test
public void java8Date() {
    DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT;
    String epoch, almostEpoch, afterEpoch;

    { // before epoch
        java.time.Instant instant = java.time.Instant.ofEpochMilli(-1);
        almostEpoch = formatter.format(instant);
        assertEquals("1969-12-31T23:59:59.999Z", almostEpoch );
    }

    { // epoch
        java.time.Instant instant = java.time.Instant.ofEpochMilli(0);
        epoch = formatter.format(instant);
        // This fails, I get 1970-01-01T00:00:00Z instead
        assertEquals("1970-01-01T00:00:00.000Z", epoch );
    }

    { // after epoch
        java.time.Instant instant = java.time.Instant.ofEpochMilli(1);
        afterEpoch = formatter.format(instant);
        assertEquals("1970-01-01T00:00:00.001Z", afterEpoch );
    }

    // The end game is to make sure this rule is respected (this is how we order things in dynamo):
    assertTrue(epoch.compareTo(almostEpoch) > 0);
    assertTrue(afterEpoch.compareTo(epoch) > 0); // <-- This assert would also fail if the second assert fails

    { // to confirm we're not showing nanos
        assertEquals("1970-01-01T00:00:00.000Z", formatter.format(Instant.EPOCH.plusNanos(1)));
        assertEquals("1970-01-01T00:00:00.001Z", formatter.format(Instant.EPOCH.plusNanos(1000000)));
    }
}

Answer

Florent picture Florent · Jun 26, 2016

OK, I looked at the the source code and it's pretty straightforward:

DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendInstant(3).toFormatter();

I hope it works for all scenarios, and it can help someone else. Don't hesitate to add a better/cleaner answer.

Just to explain where it comes from, in the JDK's code,

ISO_INSTANT is defined like this:

public static final DateTimeFormatter ISO_INSTANT;
static {
    ISO_INSTANT = new DateTimeFormatterBuilder()
            .parseCaseInsensitive()
            .appendInstant()
            .toFormatter(ResolverStyle.STRICT, null);
}

And DateTimeFormatterBuilder::appendInstant is declared as:

public DateTimeFormatterBuilder appendInstant() {
    appendInternal(new InstantPrinterParser(-2));
    return this;
}

And the constructor InstantPrinterParser signature is:

InstantPrinterParser(int fractionalDigits)