Skip to content

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

Closed

Description

@Strongbeard

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 org.junit.jupiter.api@5.11.4/org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
        at org.junit.jupiter.api@5.11.4/org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
        at org.junit.jupiter.api@5.11.4/org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
        at org.junit.jupiter.api@5.11.4/org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
        at org.junit.jupiter.api@5.11.4/org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
        at org.junit.jupiter.api@5.11.4/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.

Activity

  1. JooHyukKim commented on Feb 25, 2025

    @JooHyukKim
    Member

    @Strongbeard my understanding is same as yours. This is bug.

    I filed #360 just so we have reproduction (runnable).
    I tried adjusting DecimalUtils.extractSecondsAndNanos() to get solution, but no luck.
    Maybe try?

  2. changed the title [-]`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`[/-] [+]`InstantDeserializer` deserializes the nanosecond portion of fractional negative timestamps incorrectly: `-1.000000001` deserializes to `1969-12-31T23:59:59.000000001Z` instead of `1969-12-31T23:59:58.999999999Z`[/+] on Nov 17, 2025
  3. added a commit that references this issue on Nov 17, 2025
    2bc6d19
  4. modified the milestones: 2.12.2, 2.21.0 on Nov 17, 2025
  5. cowtowncoder commented on Nov 17, 2025

    @cowtowncoder
    Member

    Fixing with Claude, see #378.

  6. added a commit that references this issue on Nov 17, 2025
    236faaf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions