In Java 11, clock system uses millisecond precision, but apparently in Java 13 and above, it uses microsecond precision and this causes my tests to fail. As an example, OffsetDateTime.now()
gives me this date "2021-12-10T10:58:05.309594500 01:00" while when I read this date from database "2021-12-10T10:58:05.309595 01:00". I am searching for a way that I can format the first date in a way that they should be equal. I do want having it in OffsetDateTime type not string.
Update: I have realised that this problem raised when I upgraded java version from 11 to 17 and not on local, I get this problem when gitlab runs the test.
this is the test:
@Test
fun `can store, find and delete a failed-message`() {
// given: a failed-message
val failedMessage = FailedMessage(
failedMessageId = FailedMessageId("the-subcription", "the-message-id"),
messageAttributes = mapOf("one" to "een", "two" to "twee"),
messagePayload = "message-payload",
exception = "exception",
dateTime = OffsetDateTime.now(),
stackTrace = "stackey tracey"
)
failedMessageRepository.store(failedMessage)
assertEquals(failedMessage, failedMessageRepository.find(failedMessage.failedMessageId))
}
this test gets failed due to not equal dateTime(s). and this is the log:
<FailedMessage(failedMessageId=the-subcription-the-message-id, messageAttributes={one=een, two=twee}, messagePayload=message-payload, exception=exception, dateTime=2021-12-10T10:58:05.309594500 01:00, stackTrace=stackey tracey)>
but was:
<FailedMessage(failedMessageId=the-subcription-the-message-id, messageAttributes={one=een, two=twee}, messagePayload=message-payload, exception=exception, dateTime=2021-12-10T10:58:05.309595 01:00, stackTrace=stackey tracey)>
Could you please help me?
CodePudding user response:
An OffsetDateTime
always has got nanosecond precision. It’s now
method may not have depending on platform and Java version. Internally the object has. So there is no way that you can have an OffsetDateTime
with fewer than 9 decimals on the seconds.
How many decimals are printed is a different question. What is really printed when you print an OffsetDateTime
is a String
. If you just print the object, its toString
method is implicitly called to produce the string that we see in print. OffsetDateTime.toString()
always gives us as many groups of 3 decimals as are necessary for rendering the full precision. So if the decimals were .100000000
, then .100
is printed. If they were .309594500
, as you have seen already, all 9 decimals are present in the string. There is no way that you can change this behaviour.
If you want a different string printed, you can use a DateTimeFormatter
with the number of decimals you like.
So what you can do:
- You can modify the
OffsetDateTime
, or really, you can create a new one like the old one only with more decimals being zero, which in turn may cause fewer decimals to be printed bytoString()
. - You can format the
OffsetDateTime
into a string of your liking.
The following code snippets combines both options. For formatting only the decimals up to the last non-zero decimal I am using the following formatter:
private static final DateTimeFormatter ODT_FORMATTER = new DateTimeFormatterBuilder()
.appendPattern("uuuu-MM-dd'T'HH:mm.ss")
.appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
.appendOffsetId()
.toFormatter(Locale.ROOT);
Demonstration:
OffsetDateTime dateTime = OffsetDateTime.parse("2021-12-10T10:58:05.309594500 01:00");
int nanos = dateTime.getNano();
OffsetDateTime with7Decimals = dateTime.withNano(nanos / 100 * 100);
System.out.println("toString(): " with7Decimals);
System.out.println("Formatted: " with7Decimals.format(ODT_FORMATTER));
OffsetDateTime with5Decimals = dateTime.withNano(nanos / 10000 * 10000);
System.out.println("toString(): " with5Decimals);
System.out.println("Formatted: " with5Decimals.format(ODT_FORMATTER));
Output:
toString(): 2021-12-10T10:58:05.309594500 01:00 Formatted: 2021-12-10T10:58.05.3095945 01:00 toString(): 2021-12-10T10:58:05.309590 01:00 Formatted: 2021-12-10T10:58.05.30959 01:00
For you tests I see no reason to use strings for comparison. Just compare the OffsetDateTime
objects after setting those decimals to zero that will be lost in your database anyway.
Edit: A workaround for your test failure? The following line is where you get the current time with a finer precision than your database can store.
dateTime = OffsetDateTime.now(),
So change it to give only the precision that your database supports:
dateTime = OffsetDateTime.now().truncatedTo(ChronoUnit.MICROS),
This should cause the time to be saved and retrieved without any further rounding.
Why did the problem arise? OffsetDateTime.now()
has different precision on different platforms and Java versions. I wasn’t aware of a difference between Java 11 and 13 on any platform, but there is no reason why there shouldn’t be one, and there may be another one in some coming version. Also if you move between Linux, Windows and Mac you will likely experience differences even with the same Java version. The other part of the problem is the limited precision of your database, or more precisely, the data type that you use in the database for storing the time. If you are using timestamp with time zone
, you are probably having the best precision that the database can offer. Apparently your database has microsecond precision (6 decimals on the seconds) and on your Java 13 OffsetDateTime.now()
has at least half microseconds (500 nanos) precision. So you since Java 13 you are getting a value that the database cannot store. And apparently your database or database driver then rounded the value up in your case. Which caused the value retrieved back from the database not to be precisely the same as the one you had tried to save.