Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

InstantDeserializer deserializes the nanosecond portion of fractional timestamps incorrectly: -1.000000001 deserializes to 1969-12-31T23:59:59.000000001Z instead of 1969-12-31T23:59:58.999999999Z #359

Open
Strongbeard opened this issue Feb 17, 2025 · 0 comments

Comments

@Strongbeard
Copy link

Strongbeard commented Feb 17, 2025

Version

2.15.4 - 2.19 and the current master branch (commit c84b1a9 as of this post).
I have not tested anything earlier than what I plan to use.

Description

Please correct me if I am misguided on how negative fractional representations of timestamps should be parsed into java.time.Instant objects. I expected -1.000000001 to deserialize to 1969-12-31T23:59:58.999999999Z, but currently it deserializes to 1969-12-31T23:59:59.000000001Z.

I assume when "splitting" the fraction into its whole seconds and nanoseconds portions, the negative sign should be distributed to both. In other words: -1.000000001 == -1 + -0.000000001, but instead the behavior appears to be -1.000000001 == -1 + 0.000000001.

Strangely, any scenario where the seconds portion is 0 deserializes as I expect it to. For example, -0.000000001 deserializes to 1969-12-31T23:59:59.999999999Z. For some reason, the distribution of the negativity from seconds to nanoseconds is preserved here.

Adding the following unit test to datetime/src/test/java/tools/jackson/datatype/jsr310/deser/InstantDeserTest.java should demonstrate the issue, where test 4 fails and test 5 succeeds:

@Test
public void testDeserializationAsFloat04() throws Exception {
    Instant actual = READER.readValue("-1.000000001");
    Instant expected = Instant.ofEpochSecond(-1L, -1L);
    assertEquals(expected, actual);
}

@Test
public void testDeserializationAsFloat05() throws Exception {
    Instant actual = READER.readValue("-0.000000001");
    Instant expected = Instant.ofEpochSecond(0L, -1L);
    assertEquals(expected, actual);
}

The following error is produced when executing the tests:

[ERROR] tools.jackson.datatype.jsr310.deser.InstantDeserTest.testDeserializationAsFloat04 -- Time elapsed: 0.128 s <<< FAILURE!
org.opentest4j.AssertionFailedError: expected: <1969-12-31T23:59:58.999999999Z> but was: <1969-12-31T23:59:59.000000001Z>
        at [email protected]/org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
        at [email protected]/org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
        at [email protected]/org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
        at [email protected]/org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
        at [email protected]/org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
        at [email protected]/org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1145)
        at tools.jackson.datatype.javatime/tools.jackson.datatype.jsr310.deser.InstantDeserTest.testDeserializationAsFloat04(InstantDeserTest.java:121)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

If my assumptions are correct, then the fix appears more complicated than just changing the negativeAdjustment boolean passed to the call to DecimalUtils.extractSecondsAndNanos(BigDecimal, BiFunction, boolean) within the _fromDecimal(DeserializationContext, BigDecimal) function of InstantDeserializer to false since that causes unit test ZonedDateTimeSerTest#testSerializationAsTimestamp01NegativeSeconds to fail.

Related Issues

I believe this is related to the implementation of the fix for issues #304, #69, and #132.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant