diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/DecimalUtils.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/DecimalUtils.java index ad9ead2f..a76c0139 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/DecimalUtils.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/DecimalUtils.java @@ -84,6 +84,14 @@ public static String toDecimal(long seconds, int nanoseconds) */ public static BigDecimal toBigDecimal(long seconds, int nanoseconds) { + // [modules-java8#359] For negative seconds with positive nanos (times before epoch), + // we need to compute the proper decimal value: seconds + (nanos / 1_000_000_000) + // Example: Instant{epochSecond=-1, nano=999000000} represents -0.001 seconds + if (seconds < 0 && nanoseconds > 0) { + return BigDecimal.valueOf(seconds) + .add(BigDecimal.valueOf(nanoseconds).scaleByPowerOfTen(-9)); + } + if (nanoseconds == 0L) { // 14-Mar-2015, tatu: Let's retain one zero to avoid interpretation // as integral number diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java index 0e1bf808..c882d541 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java @@ -480,8 +480,9 @@ protected T _fromDecimal(DeserializationContext context, BigDecimal value) { FromDecimalArguments args = DecimalUtils.extractSecondsAndNanos(value, (s, ns) -> new FromDecimalArguments(s, ns, getZone(context)), - // [modules-java8#337] since 2.19, only Instant needs negative adjustment - true); + // [modules-java8#359] since 2.21, Instant.ofEpochSecond() correctly handles + // negative nanoseconds, so no adjustment needed + false); return fromNanoseconds.apply(args); } diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/tofix/InstantDeserializerNegative359Test.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializerNegative359Test.java similarity index 86% rename from datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/tofix/InstantDeserializerNegative359Test.java rename to datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializerNegative359Test.java index 44ce23bf..ac4e5089 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/tofix/InstantDeserializerNegative359Test.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializerNegative359Test.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.datatype.jsr310.tofix; +package com.fasterxml.jackson.datatype.jsr310.deser; import java.time.Instant; @@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase; -import com.fasterxml.jackson.datatype.jsr310.testutil.failure.JacksonTestFailureExpected; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -18,7 +17,6 @@ public class InstantDeserializerNegative359Test { private final ObjectReader READER = newMapper().readerFor(Instant.class); - @JacksonTestFailureExpected @Test public void testDeserializationAsFloat04() throws Exception diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 39c41bf9..f75b3900 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -213,6 +213,9 @@ Kevin Mahon (@Strongbeard) * Fixed #291: `InstantDeserializer` fails to parse negative numeric timestamp strings for pre-1970 values (2.18.4) + * Reported #359: `InstantDeserializer` deserializes the nanosecond portion of fractional + negative timestamps incorrectly + (2.21.0) Joey Muia (@jmuia) * Reported #337: Negative `Duration` does not round-trip properly with diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index dd8ac134..f830b290 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -10,6 +10,11 @@ Modules: 2.21.0 (not yet released) +#359: `InstantDeserializer` deserializes the nanosecond portion of fractional + negative timestamps incorrectly + (reported by Kevin M) + (fix by @cowtowncoder, w/ Claude code) + No changes since 2.20 2.20.1 (30-Oct-2025)