Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LocalDateTime to milliseconds difference in Java 8 and Java 11

I'm currently in the process of upgrading a few projects from Java 8 to Java 11 where one of the unit tests for a converter failed. Basically the problem stems from the equality check failing due to a a date precision which previously passed with JDK 8.

Here's a section sample of the test, I've moved the contents of the converter for clarity:

@Test
public void testDateTime() {
    LocalDateTime expected = LocalDateTime.now().plusDays(1L);

    // converter contents
    long epochMillis = expected.atZone(ZoneId.systemDefault())
            .toInstant().toEpochMilli();
    LocalDateTime actual = LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMillis),
            TimeZone.getDefault().toZoneId());


    assertThat(actual, equalTo(expected));
}

This results to an assetion error due to:

Expected :<2021-06-02T14:06:21.820299>
Actual   :<2021-06-02T14:06:21.820>

I can trunkate the expected with assertThat(actual, equalTo(expected.truncatedTo(ChronoUnit.MILLIS))) for them to be equal, however, this means each time a comparison is made (isAfter, isBefore, equals) to the converter class being tested, trunkating will have to be applied.

Is there a proper way to do conversions for between LocalDateTime to Long and vice versa for JDK 11 (or a documentation I might have missed perhaps :))?


Update:

As pointed out in the comments, the representation is not the same for Java 8 and 11 thus resulting to the failing test. To give more context on what is being asked by this post, here are the 2 methods being verified by the test (which i moved to the test itself to only capture what is being executed because the unit test that failed belongs to a class that uses the utility method)

public Long localDateTimeToEpochMillis(LocalDateTime ldt) {
    Instant instant = ldt.atZone(ZoneId.systemDefault()).toInstant();
        return ldt.atZone(ZoneId.systemDefault())
           .toInstant().toEpochMilli();
}

and

public LocalDateTime epochMillisToLocalDateTime(long epochMillis) {
    return LocalDateTime.ofInstant(
            Instant.ofEpochMilli(epochMillis), 
            ZoneId.systemDefault());
}

What the existng tests seems to verify is that given a long value, i should get the same LocalDateTime equivalent and this was done by using the Given (LocalDateTime converted to Long value) then back to LocalDateTime for comparison.

like image 235
geneqew Avatar asked Jun 01 '21 06:06

geneqew


1 Answers

If you take a look at the difference:

Expected :<2021-06-02T14:06:21.820299>
Actual   :<2021-06-02T14:06:21.820>

You can see that it removes anything less than a millisecond.

This happens because you convert the LocalDateTime to milliseconds:

.toInstant().toEpochMilli();

In order to avoid that, you can use Instant#getNano:

Gets the number of nanoseconds, later along the time-line, from the start of the second. The nanosecond-of-second value measures the total number of nanoseconds from the second returned by getEpochSecond().

It could look like this:

Instant instant=expected.atZone(ZoneId.systemDefault())
                .toInstant();
long epochMillis = instant.toEpochMilli();
long nanos=instant.getNano()%1000000;//get nanos of Millisecond

LocalDateTime actual = LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMillis).plusNanos(nanos),
        TimeZone.getDefault().toZoneId());

Why did it work in Java 8?

As this post and JDK-8068730 Increase the precision of the implementation of java.time.Clock.systemUTC() describes, Java 8 did not capture time units smaller than a millisecond. Since Java 9, LocalDateTime.now (and similar) get the time with microseconds.

like image 58
dan1st Avatar answered Oct 05 '22 22:10

dan1st