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 601f5962..22c0b564 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 @@ -17,6 +17,7 @@ package com.fasterxml.jackson.datatype.jsr310; import java.math.BigDecimal; +import java.time.Instant; import java.util.function.BiFunction; /** @@ -136,6 +137,11 @@ else if (seconds.scale() < -63) { // Now we know that seconds has reasonable scale, we can safely chop it apart. secondsOnly = seconds.longValue(); nanosOnly = nanoseconds.subtract(new BigDecimal(secondsOnly).scaleByPowerOfTen(9)).intValue(); + + if (secondsOnly < 0 && secondsOnly > Instant.MIN.getEpochSecond()) { + // Issue #69 and Issue #120: avoid sending a negative adjustment to the Instant constructor, we want this as the actual nanos + nanosOnly = Math.abs(nanosOnly); + } } return convert.apply(secondsOnly, nanosOnly); diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestDecimalUtils.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestDecimalUtils.java index f801f46c..0b49a0c6 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestDecimalUtils.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestDecimalUtils.java @@ -27,6 +27,10 @@ public void testToDecimal01() decimal = DecimalUtils.toDecimal(19827342231L, 999888000); assertEquals("The returned decimal is not correct.", "19827342231.999888000", decimal); + + decimal = DecimalUtils.toDecimal(-22704862, 599000000); + assertEquals("The returned decimal is not correct.", + "-22704862.599000000", decimal); } @SuppressWarnings("deprecation") @@ -81,7 +85,6 @@ public void testExtractNanosecondDecimal06() checkExtractNanos(19827342231L, 999999999, value); } - private void checkExtractSecondsAndNanos(long expectedSeconds, int expectedNanos, BigDecimal decimal) { DecimalUtils.extractSecondsAndNanos(decimal, (Long s, Integer ns) -> { @@ -133,6 +136,13 @@ public void testExtractSecondsAndNanos06() checkExtractSecondsAndNanos(19827342231L, 999999999, value); } + @Test + public void testExtractSecondsAndNanosFromNegativeBigDecimal() + { + BigDecimal value = new BigDecimal("-22704862.599000000"); + checkExtractSecondsAndNanos(-22704862L, 599000000, value); + } + @Test(timeout = 100) public void testExtractSecondsAndNanos07() { diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestZonedDateTimeSerialization.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestZonedDateTimeSerialization.java index 2ee35d7c..bbfb4bfe 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestZonedDateTimeSerialization.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestZonedDateTimeSerialization.java @@ -89,6 +89,30 @@ public void testSerializationAsTimestamp01Nanoseconds() throws Exception assertEquals("The value is not correct.", "0.0", value); } + @Test + public void testSerializationAsTimestamp01NegativeSeconds() throws Exception + { + // test for Issue #69 + ZonedDateTime date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(-14159020000L, 183917322), UTC); + String serialized = MAPPER.writer() + .with(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .with(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .writeValueAsString(date); + ZonedDateTime actual = MAPPER.readValue(serialized, ZonedDateTime.class); + assertEquals("The value is not correct.", date, actual); + } + + @Test + public void testSerializationAsTimestamp01NegativeSecondsWithDefaults() throws Exception + { + // test for Issue #69 using default mapper config + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("MMM dd yyyy HH:mm:ss.SSS zzz"); + ZonedDateTime original = ZonedDateTime.parse("Apr 13 1969 05:05:38.599 UTC", dtf); + String serialized = MAPPER.writeValueAsString(original); + ZonedDateTime deserialized = MAPPER.readValue(serialized, ZonedDateTime.class); + assertEquals("The value is not correct.", original, deserialized); + } + @Test public void testSerializationAsTimestamp01Milliseconds() throws Exception { @@ -751,6 +775,16 @@ public void testNumericCustomPatternWithAnnotations() throws Exception assertEquals(input.value.toInstant(), result.value.toInstant()); } + @Test + public void testInstantPriorToEpochIsEqual() throws Exception + { + //Issue #120 test + final Instant original = Instant.ofEpochMilli(-1); + final String serialized = MAPPER.writeValueAsString(original); + final Instant deserialized = MAPPER.readValue(serialized, Instant.class); + assertEquals(original, deserialized); + } + private static void assertIsEqual(ZonedDateTime expected, ZonedDateTime actual) { assertTrue("The value is not correct. Expected timezone-adjusted <" + expected + ">, actual <" + actual + ">.",