From a77105486289e89fbfd39a7f004e26cc6f26b1f9 Mon Sep 17 00:00:00 2001 From: liedtkem Date: Sun, 14 Jun 2020 13:55:25 -0700 Subject: [PATCH 1/2] Add support to (de)serialize Ion timestamps with java.time Instant, OffsetDateTime, and ZonedDateTime classes. Set JDK version to 8. --- ion/README.md | 11 + ion/pom.xml | 2 + .../ion/jsr310/IonJavaTimeModule.java | 43 ++ .../IonTimestampInstantDeserializer.java | 132 ++++++ .../jsr310/IonTimestampInstantSerializer.java | 139 ++++++ .../dataformat/ion/jsr310/TimestampUtils.java | 85 ++++ .../IonTimestampInstantDeserializerTest.java | 320 +++++++++++++ .../IonTimestampInstantSerializerTest.java | 176 ++++++++ ...mestampOffsetDateTimeDeserializerTest.java | 416 +++++++++++++++++ ...TimestampOffsetDateTimeSerializerTest.java | 176 ++++++++ ...imestampZonedDateTimeDeserializerTest.java | 420 ++++++++++++++++++ ...nTimestampZonedDateTimeSerializerTest.java | 176 ++++++++ .../ion/jsr310/MockObjectConfiguration.java | 23 + 13 files changed, 2119 insertions(+) create mode 100644 ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonJavaTimeModule.java create mode 100644 ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantDeserializer.java create mode 100644 ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantSerializer.java create mode 100644 ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/TimestampUtils.java create mode 100644 ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantDeserializerTest.java create mode 100644 ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantSerializerTest.java create mode 100644 ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampOffsetDateTimeDeserializerTest.java create mode 100644 ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampOffsetDateTimeSerializerTest.java create mode 100644 ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampZonedDateTimeDeserializerTest.java create mode 100644 ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampZonedDateTimeSerializerTest.java create mode 100644 ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/MockObjectConfiguration.java diff --git a/ion/README.md b/ion/README.md index 34d7053f0..df17e4717 100644 --- a/ion/README.md +++ b/ion/README.md @@ -32,6 +32,17 @@ byte[] encoded = mapper.writeValueAsBytes(value); SomeType otherValue = mapper.readValue(data, SomeType.class); ``` +### java.time JSR 310 +There is support for (de)serializing some `java.time` classes directly from/to Ion timestamp values. + +```java +IonObjectMapper mapper = IonObjectMapper.builder() + .addModule(new IonJavaTimeModule()) + //Disable writing dates as numeric timestamp values to allow writing as Ion timestamp values. + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); +``` + ## Documentation See [Wiki](../../../wiki) (includes Javadocs) diff --git a/ion/pom.xml b/ion/pom.xml index f5f4fdd24..c7cc43079 100644 --- a/ion/pom.xml +++ b/ion/pom.xml @@ -19,6 +19,8 @@ tree model) com/fasterxml/jackson/dataformat/ion ${project.groupId}.ion + 1.8 + 1.8 diff --git a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonJavaTimeModule.java b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonJavaTimeModule.java new file mode 100644 index 000000000..1ad2a4ae9 --- /dev/null +++ b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonJavaTimeModule.java @@ -0,0 +1,43 @@ +package com.fasterxml.jackson.dataformat.ion.jsr310; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; + +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.core.json.PackageVersion; +import com.fasterxml.jackson.databind.module.SimpleModule; + +/** + * A module that installs a collection of serializers and deserializers for java.time classes. + */ +public class IonJavaTimeModule extends SimpleModule { + + private static final long serialVersionUID = 1L; + + public IonJavaTimeModule() { + super(PackageVersion.VERSION); + addSerializer(Instant.class, IonTimestampInstantSerializer.INSTANT); + addSerializer(OffsetDateTime.class, IonTimestampInstantSerializer.OFFSET_DATE_TIME); + addSerializer(ZonedDateTime.class, IonTimestampInstantSerializer.ZONED_DATE_TIME); + + addDeserializer(Instant.class, IonTimestampInstantDeserializer.INSTANT); + addDeserializer(OffsetDateTime.class, IonTimestampInstantDeserializer.OFFSET_DATE_TIME); + addDeserializer(ZonedDateTime.class, IonTimestampInstantDeserializer.ZONED_DATE_TIME); + } + + @Override + public String getModuleName() { + return getClass().getName(); + } + + @Override + public Version version() { + return PackageVersion.VERSION; + } + + @Override + public void setupModule(SetupContext context) { + super.setupModule(context); + } +} diff --git a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantDeserializer.java b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantDeserializer.java new file mode 100644 index 000000000..73df0be9e --- /dev/null +++ b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantDeserializer.java @@ -0,0 +1,132 @@ +package com.fasterxml.jackson.dataformat.ion.jsr310; + +import java.io.IOException; +import java.math.BigDecimal; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.temporal.Temporal; +import java.util.function.BiFunction; + +import com.amazon.ion.Timestamp; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Feature; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; + +/** + * A deserializer for variants of java.time classes that represent a specific instant on the timeline + * (Instant, OffsetDateTime, ZonedDateTime) which supports deserializing from an Ion timestamp value. + * + * @param The type of a instant class that can be deserialized. + */ +public class IonTimestampInstantDeserializer extends StdScalarDeserializer + implements ContextualDeserializer { + + private static final long serialVersionUID = 1L; + + public static final IonTimestampInstantDeserializer INSTANT = + new IonTimestampInstantDeserializer<>(Instant.class, (instant, zoneID) -> instant); + + public static final IonTimestampInstantDeserializer OFFSET_DATE_TIME = + new IonTimestampInstantDeserializer<>(OffsetDateTime.class, OffsetDateTime::ofInstant); + + public static final IonTimestampInstantDeserializer ZONED_DATE_TIME = + new IonTimestampInstantDeserializer<>(ZonedDateTime.class, ZonedDateTime::ofInstant); + + protected final BiFunction fromInstant; + + /** + * Flag for JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE + */ + protected final Boolean adjustToContextTZOverride; + + protected IonTimestampInstantDeserializer(Class vc, BiFunction fromInstant) { + super(vc); + this.fromInstant = fromInstant; + this.adjustToContextTZOverride = null; + } + + protected IonTimestampInstantDeserializer(IonTimestampInstantDeserializer base, + Boolean adjustToContextTZOverride) { + + super(base.handledType()); + this.fromInstant = base.fromInstant; + this.adjustToContextTZOverride = adjustToContextTZOverride; + } + + @SuppressWarnings("unchecked") + @Override + public T deserialize(JsonParser p, DeserializationContext context) throws IOException, JsonProcessingException { + final ZoneId defaultZoneId = context.getTimeZone().toZoneId().normalized(); + switch (p.getCurrentToken()) { + case VALUE_NUMBER_FLOAT: + return fromDecimal(p.getDecimalValue(), defaultZoneId); + case VALUE_NUMBER_INT: + return fromLong(p.getLongValue(), defaultZoneId, context); + case VALUE_EMBEDDED_OBJECT: + final Object embeddedObject = p.getEmbeddedObject(); + if (Timestamp.class.isAssignableFrom(embeddedObject.getClass())) { + return fromTimestamp((Timestamp)embeddedObject, defaultZoneId); + } + default: + try { + return (T) context.handleUnexpectedToken(_valueClass, p); + } catch (JsonMappingException e) { + throw e; + } catch (IOException e) { + throw JsonMappingException.fromUnexpectedIOE(e); + } + } + } + + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) + throws JsonMappingException { + + final JsonFormat.Value format = findFormatOverrides(ctxt, property, handledType()); + if (format != null) { + return new IonTimestampInstantDeserializer(this, + format.getFeature(Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)); + } + return this; + } + + private T fromDecimal(BigDecimal decimalValue, ZoneId defaultZoneId) { + final Instant instant = TimestampUtils.fromFractionalSeconds(decimalValue); + return fromInstant.apply(instant, defaultZoneId); + } + + private T fromLong(long longValue, ZoneId defaultZoneId, DeserializationContext context) { + if(context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)){ + return fromInstant.apply(Instant.ofEpochSecond(longValue, 0), defaultZoneId); + } + return fromInstant.apply(Instant.ofEpochMilli(longValue), defaultZoneId); + } + + private T fromTimestamp(Timestamp timestamp, ZoneId defaultZoneId) { + final Instant instant = TimestampUtils.toInstant(timestamp); + final ZoneId zoneId = getZoneId(timestamp, defaultZoneId); + return fromInstant.apply(instant, zoneId); + } + + private ZoneId getZoneId(Timestamp timestamp, ZoneId defaultZoneId) { + if (Boolean.TRUE.equals(adjustToContextTZOverride) + || null == timestamp.getLocalOffset() + || Instant.class.equals(_valueClass)) { + + return defaultZoneId; + } + final int localOffsetMinutes = timestamp.getLocalOffset(); + return ZoneOffset.ofTotalSeconds(localOffsetMinutes * 60); + } +} diff --git a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantSerializer.java b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantSerializer.java new file mode 100644 index 000000000..9fcd852cc --- /dev/null +++ b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantSerializer.java @@ -0,0 +1,139 @@ +package com.fasterxml.jackson.dataformat.ion.jsr310; + +import java.io.IOException; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.temporal.Temporal; +import java.util.function.BiFunction; +import java.util.function.Function; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Feature; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer; +import com.fasterxml.jackson.dataformat.ion.IonGenerator; + +/** + * A serializer for variants of java.time classes that represent a specific instant on the timeline + * (Instant, OffsetDateTime, ZonedDateTime) which supports serializing to an Ion timestamp value. + * + * @param The type of a instant class that can be serialized. + */ +public class IonTimestampInstantSerializer extends StdScalarSerializer + implements ContextualSerializer { + + private static final long serialVersionUID = 1L; + + public static final IonTimestampInstantSerializer INSTANT = + new IonTimestampInstantSerializer<>(Instant.class, + Function.identity(), + (instant) -> ZoneOffset.UTC, + (instant, zoneId) -> instant.atZone(zoneId).getOffset()); + + public static final IonTimestampInstantSerializer OFFSET_DATE_TIME = + new IonTimestampInstantSerializer<>(OffsetDateTime.class, + OffsetDateTime::toInstant, + OffsetDateTime::getOffset, + (offsetDateTime, zoneId) -> offsetDateTime.atZoneSameInstant(zoneId).getOffset()); + + /** + * A serializer for ZoneDateTime's. NOTE: Ion timestamp values can only represent offset values + * so specific time zone values will be converted to an equivalent offset value. + */ + public static final IonTimestampInstantSerializer ZONED_DATE_TIME = + new IonTimestampInstantSerializer<>(ZonedDateTime.class, + ZonedDateTime::toInstant, + ZonedDateTime::getOffset, + (zonedDateTime, zoneId) -> zonedDateTime.withZoneSameInstant(zoneId).getOffset()); + + private final Function getInstant; + private final Function getOffset; + private final BiFunction getOffsetAtZoneId; + + /** + * ZoneId equivalent of JsonFormat.timezone + */ + private final ZoneId zoneIdOverride; + + /** + * Flag for JsonFormat.Feature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS + */ + private final Boolean writeDateTimestampsAsNanosOverride; + + protected IonTimestampInstantSerializer(Class t, + Function getInstant, + Function getOffset, + BiFunction getOffsetAtZoneId) { + + super(t); + this.getInstant = getInstant; + this.getOffset = getOffset; + this.getOffsetAtZoneId = getOffsetAtZoneId; + this.zoneIdOverride = null; + this.writeDateTimestampsAsNanosOverride = null; + } + + protected IonTimestampInstantSerializer(IonTimestampInstantSerializer base, + ZoneId zoneIdOverride, + Boolean writeDateTimestampsAsNanosOverride) { + + super(base.handledType()); + this.getInstant = base.getInstant; + this.getOffset = base.getOffset; + this.getOffsetAtZoneId = base.getOffsetAtZoneId; + this.zoneIdOverride = zoneIdOverride; + this.writeDateTimestampsAsNanosOverride = writeDateTimestampsAsNanosOverride; + } + + @Override + public void serialize(T value, JsonGenerator gen, SerializerProvider provider) throws IOException { + final Instant instant = getInstant.apply(value); + if (provider.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) { + if (shouldWriteTimestampsAsNanos(provider)) { + gen.writeNumber(TimestampUtils.getFractionalSeconds(instant)); + } else { + gen.writeNumber(instant.toEpochMilli()); + } + } else { + final ZoneOffset offset = getOffset(value); + ((IonGenerator)gen).writeValue(TimestampUtils.toTimestamp(instant, offset)); + } + } + + @Override + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) + throws JsonMappingException { + + final JsonFormat.Value format = findFormatOverrides(prov, property, handledType()); + if (format != null) { + return new IonTimestampInstantSerializer<>(this, + format.getTimeZone() == null ? null : format.getTimeZone().toZoneId(), + format.getFeature(Feature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS)); + } + return this; + } + + private boolean shouldWriteTimestampsAsNanos(SerializerProvider provider) { + if (Boolean.FALSE.equals(writeDateTimestampsAsNanosOverride)) { + return false; + } + return provider.isEnabled(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + || Boolean.TRUE.equals(writeDateTimestampsAsNanosOverride); + } + + private ZoneOffset getOffset(T value) { + if (null != zoneIdOverride) { + return getOffsetAtZoneId.apply(value, zoneIdOverride); + } + return getOffset.apply(value); + } +} diff --git a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/TimestampUtils.java b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/TimestampUtils.java new file mode 100644 index 000000000..28dec3e93 --- /dev/null +++ b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/TimestampUtils.java @@ -0,0 +1,85 @@ +package com.fasterxml.jackson.dataformat.ion.jsr310; + +import java.math.BigDecimal; +import java.time.Instant; +import java.time.ZoneOffset; + +import com.amazon.ion.Timestamp; + +final class TimestampUtils { + + private static final BigDecimal ONE_THOUSAND = new BigDecimal("1000"); + private static final BigDecimal ONE_MILLION = new BigDecimal("1000000"); + private static final BigDecimal ONE_BILLION = new BigDecimal("1000000000"); + + private TimestampUtils() {} + + static Timestamp toTimestamp(Instant instant, ZoneOffset offset) { + final Integer offsetMinutes = offset == null ? null + : secondsToMinutes(offset.getTotalSeconds()); + + return Timestamp.forMillis(getFractionalMillis(instant), offsetMinutes); + } + + static Instant toInstant(Timestamp timestamp) { + final BigDecimal decSeconds = timestamp.getDecimalMillis().divide(ONE_THOUSAND); + final long epocSeconds = decSeconds.longValue(); + final long nanoAdjustment = decSeconds.subtract(BigDecimal.valueOf(epocSeconds)) + .multiply(ONE_BILLION) + .longValue(); + + return Instant.ofEpochSecond(epocSeconds, nanoAdjustment); + } + + static BigDecimal getFractionalSeconds(Instant instant) { + final BigDecimal epochSeconds = BigDecimal.valueOf(instant.getEpochSecond()); + final BigDecimal nanos = BigDecimal.valueOf(instant.getNano()); + + return epochSeconds.add(nanos.divide(ONE_BILLION)); + } + + static BigDecimal getFractionalMillis(Instant instant) { + final BigDecimal epochSeconds = BigDecimal.valueOf(instant.getEpochSecond()); + final BigDecimal nanos = BigDecimal.valueOf(instant.getNano()); + + return epochSeconds.multiply(ONE_THOUSAND) + .add(nanos.divide(ONE_MILLION)); + } + + //From https://github.com/FasterXML/jackson-modules-java8 + static Instant fromFractionalSeconds(BigDecimal seconds) { + // Complexity is here to workaround unbounded latency in some BigDecimal operations. + // https://github.com/FasterXML/jackson-databind/issues/2141 + + long secondsOnly; + int nanosOnly; + + BigDecimal nanoseconds = seconds.scaleByPowerOfTen(9); + if (nanoseconds.precision() - nanoseconds.scale() <= 0) { + // There are no non-zero digits to the left of the decimal point. + // This protects against very negative exponents. + secondsOnly = nanosOnly = 0; + } + else if (seconds.scale() < -63) { + // There would be no low-order bits once we chop to a long. + // This protects against very positive exponents. + secondsOnly = nanosOnly = 0; + } + else { + // 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 Instant.ofEpochSecond(secondsOnly, nanosOnly) ; + } + + private static int secondsToMinutes(int seconds) { + return Math.floorDiv(seconds, 60); + } +} diff --git a/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantDeserializerTest.java b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantDeserializerTest.java new file mode 100644 index 000000000..268f15db6 --- /dev/null +++ b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantDeserializerTest.java @@ -0,0 +1,320 @@ +package com.fasterxml.jackson.dataformat.ion.jsr310; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.time.DateTimeException; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Temporal; + +import org.junit.Test; + +import com.amazon.ion.Timestamp; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.dataformat.ion.IonObjectMapper; + +public class IonTimestampInstantDeserializerTest { + + private static final IonObjectMapper MAPPER = IonObjectMapper.builder() + .addModule(new IonJavaTimeModule()) + .build(); + + private static final ObjectReader READER = MAPPER.readerFor(Instant.class); + + private IonObjectMapper.Builder newMapperBuilder() { + return IonObjectMapper.builder() + .addModule(new IonJavaTimeModule()); + } + + /* + ********************************************************************** + * Deserialization from decimal actual (seconds with fractions) + ********************************************************************** + */ + + @Test + public void testDeserializationAsFloat01() throws Exception { + Instant expected = Instant.ofEpochSecond(0L); + Instant actual = READER.readValue("0.000000"); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsFloat02() throws Exception { + Instant expected = Instant.ofEpochSecond(123456789L, 183917322); + Instant actual = READER.readValue("123456789.183917322"); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsFloat03() throws Exception { + Instant expected = Instant.now(); + Instant actual = READER.readValue(TimestampUtils.getFractionalSeconds(expected).toString()); + assertEquals("The value is not correct.", expected, actual); + } + + /** + * Test the upper-bound of Instant. + */ + @Test + public void testDeserializationAsFloatEdgeCase01() throws Exception { + String input = Instant.MAX.getEpochSecond() + ".999999999"; + Instant actual = READER.readValue(input); + assertEquals(actual, Instant.MAX); + assertEquals(Instant.MAX.getEpochSecond(), actual.getEpochSecond()); + assertEquals(999999999, actual.getNano()); + } + + /** + * Test the lower-bound of Instant. + */ + @Test + public void testDeserializationAsFloatEdgeCase02() throws Exception { + String input = Instant.MIN.getEpochSecond() + ".0"; + Instant actual = READER.readValue(input); + assertEquals(actual, Instant.MIN); + assertEquals(Instant.MIN.getEpochSecond(), actual.getEpochSecond()); + assertEquals(0, actual.getNano()); + } + + @Test(expected = DateTimeException.class) + public void testDeserializationAsFloatEdgeCase03() throws Exception { + // Instant can't go this low + String input = Instant.MIN.getEpochSecond() + ".1"; + READER.readValue(input); + } + + @Test(expected = DateTimeException.class) + public void testDeserializationAsFloatEdgeCase04() throws Exception { + // 1s beyond the upper-bound of Instant. + String input = (Instant.MAX.getEpochSecond() + 1) + ".0"; + READER.readValue(input); + } + + @Test(expected = DateTimeException.class) + public void testDeserializationAsFloatEdgeCase05() throws Exception { + // 1s beyond the lower-bound of Instant. + String input = (Instant.MIN.getEpochSecond() - 1) + ".0"; + READER.readValue(input); + } + + @Test + public void testDeserializationAsFloatEdgeCase06() throws Exception { + // Into the positive zone where everything becomes zero. + Instant actual = READER.readValue("1e64"); + assertEquals(0, actual.getEpochSecond()); + } + + @Test + public void testDeserializationAsFloatEdgeCase07() throws Exception { + // Into the negative zone where everything becomes zero. + Instant actual = READER.readValue("-1e64"); + assertEquals(0, actual.getEpochSecond()); + } + + /** + * Numbers with very large exponents can take a long time, but still result in zero. + * https://github.com/FasterXML/jackson-databind/issues/2141 + */ + @Test(timeout = 100) + public void testDeserializationAsFloatEdgeCase08() throws Exception { + Instant actual = READER.readValue("1e308"); + assertEquals(0, actual.getEpochSecond()); + } + + @Test(timeout = 100) + public void testDeserializationAsFloatEdgeCase09() throws Exception { + Instant actual = READER.readValue("-1e308"); + assertEquals(0, actual.getEpochSecond()); + } + + /** + * Same for large negative exponents. + */ + @Test(timeout = 100) + public void testDeserializationAsFloatEdgeCase10() throws Exception { + Instant actual = READER.readValue("1e-323"); + assertEquals(0, actual.getEpochSecond()); + } + + @Test(timeout = 100) + public void testDeserializationAsFloatEdgeCase11() throws Exception { + Instant actual = READER.readValue("-1e-323"); + assertEquals(0, actual.getEpochSecond()); + } + + /* + ********************************************************************** + * Deserialization from int actual (milliseconds) + ********************************************************************** + */ + + @Test + public void testDeserializationAsInt01Nanoseconds() throws Exception { + Instant expected = Instant.ofEpochSecond(0L); + Instant actual = READER + .with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("0"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt02Nanoseconds() throws Exception { + Instant expected = Instant.ofEpochSecond(123456789L); + Instant actual = READER + .with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("123456789"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt03Nanoseconds() throws Exception { + Instant expected = Instant.now().truncatedTo(ChronoUnit.SECONDS); + Instant actual = READER + .with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue(Long.toString(expected.getEpochSecond())); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt01Milliseconds() throws Exception { + Instant expected = Instant.ofEpochSecond(0L); + Instant actual = READER + .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("0"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt02Milliseconds() throws Exception { + Instant expected = Instant.ofEpochSecond(123456789L, 422000000); + Instant actual = READER + .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("123456789422"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt03Milliseconds() throws Exception { + Instant expected = Instant.now().truncatedTo(ChronoUnit.MILLIS); + Instant actual = READER + .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue(Long.toString(expected.toEpochMilli())); + + assertEquals("The value is not correct.", expected, actual); + } + + /* + ********************************************************************** + * Deserialization from Ion timestamp actual + ********************************************************************** + */ + + @Test + public void testDeserializationAsIonTimestamp01() throws Exception { + Instant expected = Instant.ofEpochSecond(0L); + Timestamp timestamp = TimestampUtils.toTimestamp(expected, ZoneOffset.UTC); + Instant actual = READER.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp02() throws Exception { + Instant expected = Instant.ofEpochSecond(123456789L, 183917322); + Timestamp timestamp = TimestampUtils.toTimestamp(expected, ZoneOffset.UTC); + Instant actual = READER.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp03() throws Exception { + Instant expected = Instant.now(); + Timestamp timestamp = TimestampUtils.toTimestamp(expected, ZoneOffset.UTC); + Instant actual = READER.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + /* + ********************************************************************** + * Deserialization of actuals with type info + ********************************************************************** + */ + + @Test + public void testDeserializationWithTypeInfo01() throws Exception { + Instant expected = Instant.ofEpochSecond(123456789L, 183917322); + IonObjectMapper m = newMapperBuilder() + .enable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Temporal actual = m.readValue("[\"" + Instant.class.getName() + "\",123456789.183917322]", Temporal.class); + assertTrue("The actual should be an Instant.", actual instanceof Instant); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo02() throws Exception { + Instant expected = Instant.ofEpochSecond(123456789L, 0); + IonObjectMapper m = newMapperBuilder() + .enable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Temporal actual = m.readValue("[\"" + Instant.class.getName() + "\",123456789]", Temporal.class); + assertTrue("The actual should be an Instant.", actual instanceof Instant); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo03() throws Exception { + Instant expected = Instant.ofEpochSecond(123456789L, 422000000); + IonObjectMapper m = newMapperBuilder() + .disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Temporal actual = m.readValue("[\"" + Instant.class.getName() + "\", 123456789422]", Temporal.class); + assertTrue("The actual should be an Instant.", actual instanceof Instant); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo04() throws Exception { + Instant expected = Instant.now(); + IonObjectMapper m = newMapperBuilder() + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Timestamp timestamp = TimestampUtils.toTimestamp(expected, ZoneOffset.UTC); + Temporal actual = m.readValue("[\"" + Instant.class.getName() + "\"," + timestamp.toString() + "]", + Temporal.class); + + assertTrue("The actual should be an Instant.", actual instanceof Instant); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationFromStringWithNonZeroOffset01() throws Exception { + Instant expected = Instant.now(); + Timestamp timestamp = TimestampUtils.toTimestamp(expected, ZoneOffset.ofHours(8)); + Instant result = READER.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, result); + } + + @Test + public void testDeserializationFromStringWithNonZeroOffset02() throws Exception { + Instant expected = Instant.now(); + Timestamp timestamp = TimestampUtils.toTimestamp(expected, ZoneOffset.ofHours(-8)); + Instant result = READER.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, result); + } +} diff --git a/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantSerializerTest.java b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantSerializerTest.java new file mode 100644 index 000000000..6ee09a250 --- /dev/null +++ b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantSerializerTest.java @@ -0,0 +1,176 @@ +package com.fasterxml.jackson.dataformat.ion.jsr310; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.math.BigDecimal; +import java.time.Instant; +import java.time.ZoneOffset; + +import org.junit.Test; + +import com.amazon.ion.IonDecimal; +import com.amazon.ion.IonInt; +import com.amazon.ion.IonTimestamp; +import com.amazon.ion.Timestamp; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.ion.IonObjectMapper; + +public class IonTimestampInstantSerializerTest { + + private IonObjectMapper.Builder newMapperBuilder() { + return IonObjectMapper.builder() + .addModule(new IonJavaTimeModule()); + } + + @Test + public void testSerializationAsTimestamp01Nanoseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + Instant date = Instant.ofEpochSecond(0L); + String value = mapper.writeValueAsString(date); + assertNotNull("The value should not be null.", value); + assertEquals("The value is not correct.", "0.", value); + } + + @Test + public void testSerializationAsTimestamp01Milliseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + Instant date = Instant.ofEpochSecond(0L); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", "0", value); + } + + @Test + public void testSerializationAsTimestamp02Nanoseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + Instant date = Instant.ofEpochSecond(123456789L, 183917322); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", "123456789.183917322", value); + } + + @Test + public void testSerializationAsTimestamp02Milliseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + Instant date = Instant.ofEpochSecond(123456789L, 183917322); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", "123456789183", value); + } + + @Test + public void testSerializationAsTimestamp03Nanoseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + Instant date = Instant.now(); + String value = mapper.writeValueAsString(date); + //TODO + assertEquals("The value is not correct.", TimestampUtils.getFractionalSeconds(date).toString(), value); + } + + @Test + public void testSerializationAsTimestamp03Milliseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + Instant date = Instant.now(); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", Long.toString(date.toEpochMilli()), value); + } + + @Test + public void testSerializationAsString01() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + Instant date = Instant.ofEpochSecond(0L); + Timestamp value = ((IonTimestamp)mapper.writeValueAsIonValue(date)).timestampValue(); + assertEquals("The value is not correct.", TimestampUtils.toTimestamp(date, ZoneOffset.UTC), value); + } + + @Test + public void testSerializationAsString02() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + Instant date = Instant.ofEpochSecond(123456789L, 183917322); + Timestamp value = ((IonTimestamp)mapper.writeValueAsIonValue(date)).timestampValue(); + assertEquals("The value is not correct.", TimestampUtils.toTimestamp(date, ZoneOffset.UTC), value); + } + + @Test + public void testSerializationAsString03() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + Instant date = Instant.now(); + Timestamp value = ((IonTimestamp)mapper.writeValueAsIonValue(date)).timestampValue(); + assertEquals("The value is not correct.", TimestampUtils.toTimestamp(date, ZoneOffset.UTC), value); + } + + @Test + public void testSerializationWithTypeInfo01() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Instant.class, MockObjectConfiguration.class) + .build(); + + Instant date = Instant.ofEpochSecond(123456789L, 183917322); + IonDecimal value = (IonDecimal) mapper.writeValueAsIonValue(date); + assertEquals("The value is not correct.", new BigDecimal("123456789.183917322"), value.bigDecimalValue()); + assertEquals("The does does not contain the expected number of annotations.", 1, value.getTypeAnnotations().length); + assertEquals("The does does not contain the expected annotation.", Instant.class.getName(), value.getTypeAnnotations()[0]); + } + + @Test + public void testSerializationWithTypeInfo02() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Instant.class, MockObjectConfiguration.class) + .build(); + + Instant date = Instant.ofEpochSecond(123456789L, 183917322); + IonInt value = (IonInt) mapper.writeValueAsIonValue(date); + assertEquals("The value is not correct.", 123456789183L, value.longValue()); + assertEquals("The does does not contain the expected number of annotations.", 1, value.getTypeAnnotations().length); + assertEquals("The does does not contain the expected annotation.", Instant.class.getName(), value.getTypeAnnotations()[0]); + } + + @Test + public void testSerializationWithTypeInfo03() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .addMixIn(Instant.class, MockObjectConfiguration.class) + .build(); + + Instant date = Instant.now(); + IonTimestamp value = (IonTimestamp) mapper.writeValueAsIonValue(date); + assertEquals("The value is not correct.", TimestampUtils.toTimestamp(date, ZoneOffset.UTC), value.timestampValue()); + assertEquals("The does does not contain the expected number of annotations.", 1, value.getTypeAnnotations().length); + assertEquals("The does does not contain the expected annotation.", Instant.class.getName(), value.getTypeAnnotations()[0]); + } +} diff --git a/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampOffsetDateTimeDeserializerTest.java b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampOffsetDateTimeDeserializerTest.java new file mode 100644 index 000000000..d95f38f0b --- /dev/null +++ b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampOffsetDateTimeDeserializerTest.java @@ -0,0 +1,416 @@ +package com.fasterxml.jackson.dataformat.ion.jsr310; + +import static java.time.ZoneOffset.UTC; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Temporal; +import java.util.TimeZone; + +import org.junit.Test; + +import com.amazon.ion.Timestamp; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.dataformat.ion.IonObjectMapper; + +public class IonTimestampOffsetDateTimeDeserializerTest { + + private static final ZoneOffset Z1 = ZoneOffset.ofHours(-8); + + private static final ObjectReader READER_UTC_DEFAULT = newMapperBuilder() + .defaultTimeZone(TimeZone.getTimeZone(UTC)) + .build() + .readerFor(OffsetDateTime.class); + + private static final ObjectReader READER_Z1_DEFAULT = newMapperBuilder() + .defaultTimeZone(TimeZone.getTimeZone(Z1)) + .build() + .readerFor(OffsetDateTime.class); + + private static IonObjectMapper.Builder newMapperBuilder() { + return IonObjectMapper.builder() + .addModule(new IonJavaTimeModule()); + } + + /* + ********************************************************************** + * Deserialization from decimal value (seconds with fractions) + ********************************************************************** + */ + + @Test + public void testDeserializationAsFloat01() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(0L), UTC); + OffsetDateTime actual = READER_UTC_DEFAULT.readValue("0.000000"); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsFloat01NonUTCDefault() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(0L), Z1); + OffsetDateTime actual = READER_Z1_DEFAULT.readValue("0.000000"); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsFloat02() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), UTC); + OffsetDateTime actual = READER_UTC_DEFAULT.readValue("123456789.183917322"); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsFloat02NonUTCDefault() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z1); + OffsetDateTime actual = READER_Z1_DEFAULT.readValue("123456789.183917322"); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsFloat03() throws Exception { + Instant now = Instant.now(); + OffsetDateTime expected = OffsetDateTime.ofInstant(now, UTC); + OffsetDateTime actual = READER_UTC_DEFAULT.readValue(TimestampUtils.getFractionalSeconds(now).toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsFloat03NonUTCDefault() throws Exception { + Instant now = Instant.now(); + OffsetDateTime expected = OffsetDateTime.ofInstant(now, Z1); + OffsetDateTime actual = READER_Z1_DEFAULT.readValue(TimestampUtils.getFractionalSeconds(now).toString()); + assertEquals("The value is not correct.", expected, actual); + } + + /* + ********************************************************************** + * Deserialization from int value (milliseconds) + ********************************************************************** + */ + + @Test + public void testDeserializationAsInt01Nanoseconds() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(0L), UTC); + OffsetDateTime actual = READER_UTC_DEFAULT + .with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("0"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt01NanosecondsNonUTCDefault() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(0L), Z1); + OffsetDateTime actual = READER_Z1_DEFAULT + .with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("0"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt02Nanoseconds() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L), UTC); + OffsetDateTime actual = READER_UTC_DEFAULT + .with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("123456789"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt02NanosecondsNonUTCDefault() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L), Z1); + OffsetDateTime actual = READER_Z1_DEFAULT + .with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("123456789"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt03Nanoseconds() throws Exception { + Instant now = Instant.now(); + OffsetDateTime expected = OffsetDateTime.ofInstant(now, UTC).truncatedTo(ChronoUnit.SECONDS); + OffsetDateTime actual = READER_UTC_DEFAULT + .with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue(Long.toString(now.getEpochSecond())); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt03NanosecondsNonUTCDefault() throws Exception { + Instant now = Instant.now(); + OffsetDateTime expected = OffsetDateTime.ofInstant(now, Z1).truncatedTo(ChronoUnit.SECONDS); + OffsetDateTime actual = READER_Z1_DEFAULT + .with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue(Long.toString(now.getEpochSecond())); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt01Milliseconds() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(0L), UTC); + OffsetDateTime actual = READER_UTC_DEFAULT + .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("0"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt01MillisecondsNonUTCDefault() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(0L), Z1); + OffsetDateTime actual = READER_Z1_DEFAULT + .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("0"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt02Milliseconds() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 422000000), UTC); + OffsetDateTime actual = READER_UTC_DEFAULT + .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("123456789422"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt02MillisecondsNonUTCDefault() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 422000000), Z1); + OffsetDateTime actual = READER_Z1_DEFAULT + .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("123456789422"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt03Milliseconds() throws Exception { + Instant now = Instant.now(); + OffsetDateTime expected = OffsetDateTime.ofInstant(now, UTC).truncatedTo(ChronoUnit.MILLIS); + OffsetDateTime actual = READER_UTC_DEFAULT + .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue(Long.toString(now.toEpochMilli())); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt03MillisecondsNonUTCDefault() throws Exception { + Instant now = Instant.now(); + OffsetDateTime expected = OffsetDateTime.ofInstant(now, Z1).truncatedTo(ChronoUnit.MILLIS); + OffsetDateTime actual = READER_Z1_DEFAULT + .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue(Long.toString(now.toEpochMilli())); + + assertEquals("The value is not correct.", expected, actual); + } + + /* + ********************************************************************** + * Deserialization from Ion timestamp value + ********************************************************************** + */ + + @Test + public void testDeserializationAsIonTimestamp01() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(0L), UTC); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), expected.getOffset()); + OffsetDateTime actual = READER_UTC_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp01NonUTCTimeOffset() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(0L), Z1); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), expected.getOffset()); + OffsetDateTime actual = READER_UTC_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp02() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), UTC); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), expected.getOffset()); + OffsetDateTime actual = READER_UTC_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp02NonUTCTimeOffset() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z1); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), expected.getOffset()); + OffsetDateTime actual = READER_UTC_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp03() throws Exception { + OffsetDateTime expected = OffsetDateTime.now(UTC); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), expected.getOffset()); + OffsetDateTime actual = READER_UTC_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp03NonUTCTimeOffset() throws Exception { + OffsetDateTime expected = OffsetDateTime.now(Z1); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), expected.getOffset()); + OffsetDateTime actual = READER_UTC_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp04UnknownOffset() throws Exception { + OffsetDateTime expected = OffsetDateTime.now(UTC); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), null); + OffsetDateTime actual = READER_UTC_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp04UnknownOffsetNonUTCDefault() throws Exception { + OffsetDateTime expected = OffsetDateTime.now(Z1); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), null); + OffsetDateTime actual = READER_Z1_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + /* + ********************************************************************** + * Deserialization from values with type info + ********************************************************************** + */ + + @Test + public void testDeserializationWithTypeInfo01() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), UTC); + + IonObjectMapper m = newMapperBuilder() + .enable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Temporal actual = m.readValue("[\"" + OffsetDateTime.class.getName() + "\",123456789.183917322]", Temporal.class); + assertTrue("The value should be an OffsetDateTime.", actual instanceof OffsetDateTime); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo01NonUTCDefault() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z1); + + IonObjectMapper m = newMapperBuilder() + .defaultTimeZone(TimeZone.getTimeZone(Z1)) + .enable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Temporal actual = m.readValue("[\"" + OffsetDateTime.class.getName() + "\",123456789.183917322]", Temporal.class); + assertTrue("The value should be an OffsetDateTime.", actual instanceof OffsetDateTime); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo02() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 0), UTC); + + IonObjectMapper m = newMapperBuilder() + .enable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Temporal actual = m.readValue("[\"" + OffsetDateTime.class.getName() + "\",123456789]", Temporal.class); + assertTrue("The value should be an OffsetDateTime.", actual instanceof OffsetDateTime); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo02NonUTCDefault() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 0), Z1); + + IonObjectMapper m = newMapperBuilder() + .defaultTimeZone(TimeZone.getTimeZone(Z1)) + .enable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Temporal actual = m.readValue("[\"" + OffsetDateTime.class.getName() + "\",123456789]", Temporal.class); + assertTrue("The value should be an OffsetDateTime.", actual instanceof OffsetDateTime); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo03() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 422000000), UTC); + + IonObjectMapper m = newMapperBuilder() + .disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Temporal actual = m.readValue("[\"" + OffsetDateTime.class.getName() + "\", 123456789422]", Temporal.class); + assertTrue("The value should be an OffsetDateTime.", actual instanceof OffsetDateTime); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo03NonUTCDefault() throws Exception { + OffsetDateTime expected = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 422000000), Z1); + + IonObjectMapper m = newMapperBuilder() + .defaultTimeZone(TimeZone.getTimeZone(Z1)) + .disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Temporal actual = m.readValue("[\"" + OffsetDateTime.class.getName() + "\", 123456789422]", Temporal.class); + assertTrue("The value should be an OffsetDateTime.", actual instanceof OffsetDateTime); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo04() throws Exception { + Instant now = Instant.now(); + OffsetDateTime expected = OffsetDateTime.ofInstant(now, UTC); + + IonObjectMapper m = newMapperBuilder() + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Timestamp timestamp = TimestampUtils.toTimestamp(now, ZoneOffset.UTC); + Temporal actual = m.readValue("[\"" + OffsetDateTime.class.getName() + "\"," + timestamp.toString() + "]", + Temporal.class); + + assertTrue("The value should be an OffsetDateTime.", actual instanceof OffsetDateTime); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo04NonUTCOffset() throws Exception { + Instant now = Instant.now(); + OffsetDateTime expected = OffsetDateTime.ofInstant(now, Z1); + + IonObjectMapper m = newMapperBuilder() + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Timestamp timestamp = TimestampUtils.toTimestamp(now, expected.getOffset()); + Temporal actual = m.readValue("[\"" + OffsetDateTime.class.getName() + "\"," + timestamp.toString() + "]", + Temporal.class); + + assertTrue("The value should be an OffsetDateTime.", actual instanceof OffsetDateTime); + assertEquals("The value is not correct.", expected, actual); + } +} diff --git a/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampOffsetDateTimeSerializerTest.java b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampOffsetDateTimeSerializerTest.java new file mode 100644 index 000000000..a7f45e183 --- /dev/null +++ b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampOffsetDateTimeSerializerTest.java @@ -0,0 +1,176 @@ +package com.fasterxml.jackson.dataformat.ion.jsr310; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.temporal.Temporal; + +import org.junit.Test; + +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.ion.IonObjectMapper; + +public class IonTimestampOffsetDateTimeSerializerTest { + + private static final ZoneOffset Z1 = ZoneOffset.ofHours(-8); + private static final ZoneOffset Z2 = ZoneOffset.ofHours(12); + private static final ZoneOffset Z3 = ZoneOffset.ofHoursMinutes(4, 30); + + private IonObjectMapper.Builder newMapperBuilder() { + return IonObjectMapper.builder() + .addModule(new IonJavaTimeModule()); + } + + @Test + public void testSerializationAsTimestamp01Nanoseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + OffsetDateTime date = OffsetDateTime.ofInstant(Instant.ofEpochSecond(0L), Z1); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", "0.", value); + } + + @Test + public void testSerializationAsTimestamp01Milliseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + OffsetDateTime date = OffsetDateTime.ofInstant(Instant.ofEpochSecond(0L), Z1); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", "0", value); + } + + @Test + public void testSerializationAsTimestamp02Nanoseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + OffsetDateTime date = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z2); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", "123456789.183917322", value); + } + + @Test + public void testSerializationAsTimestamp02Milliseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + OffsetDateTime date = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z2); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", "123456789183", value); + } + + @Test + public void testSerializationAsTimestamp03Nanoseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + OffsetDateTime date = OffsetDateTime.now(Z3); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", TimestampUtils.getFractionalSeconds(date.toInstant()).toString(), value); + } + + @Test + public void testSerializationAsTimestamp03Milliseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + OffsetDateTime date = OffsetDateTime.now(Z3); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", Long.toString(date.toInstant().toEpochMilli()), value); + } + + @Test + public void testSerializationAsString01() throws Exception { + OffsetDateTime date = OffsetDateTime.ofInstant(Instant.ofEpochSecond(0L), Z1); + IonObjectMapper mapper = newMapperBuilder() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", + TimestampUtils.toTimestamp(date.toInstant(), date.getOffset()).toString(), value); + } + + @Test + public void testSerializationAsString02() throws Exception { + OffsetDateTime date = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z2); + IonObjectMapper mapper = newMapperBuilder() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", + TimestampUtils.toTimestamp(date.toInstant(), date.getOffset()).toString(), value); + } + + @Test + public void testSerializationAsString03() throws Exception { + OffsetDateTime date = OffsetDateTime.now(Z3); + IonObjectMapper mapper = newMapperBuilder() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", + TimestampUtils.toTimestamp(date.toInstant(), date.getOffset()).toString(), value); + } + + @Test + public void testSerializationWithTypeInfo01() throws Exception { + OffsetDateTime date = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z2); + IonObjectMapper mapper = newMapperBuilder() + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", + "'" + OffsetDateTime.class.getName() + "'::123456789.183917322", value); + } + + @Test + public void testSerializationWithTypeInfo02() throws Exception { + OffsetDateTime date = OffsetDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z2); + IonObjectMapper mapper = newMapperBuilder() + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", + "'" + OffsetDateTime.class.getName() + "'::123456789183", value); + } + + @Test + public void testSerializationWithTypeInfo03() throws Exception { + OffsetDateTime date = OffsetDateTime.now(Z3); + IonObjectMapper mapper = newMapperBuilder() + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + String value = mapper.writeValueAsString(date); + assertNotNull("The value should not be null.", value); + assertEquals("The value is not correct.","'" + OffsetDateTime.class.getName() + "'::" + + TimestampUtils.toTimestamp(date.toInstant(), date.getOffset()).toString(), value); + } +} diff --git a/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampZonedDateTimeDeserializerTest.java b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampZonedDateTimeDeserializerTest.java new file mode 100644 index 000000000..0ff0a9f66 --- /dev/null +++ b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampZonedDateTimeDeserializerTest.java @@ -0,0 +1,420 @@ +package com.fasterxml.jackson.dataformat.ion.jsr310; + +import static java.time.ZoneOffset.UTC; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.time.Instant; +import java.time.ZonedDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Temporal; +import java.util.TimeZone; + +import org.junit.Test; + +import com.amazon.ion.Timestamp; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.dataformat.ion.IonObjectMapper; + +public class IonTimestampZonedDateTimeDeserializerTest { + + private static final ZoneOffset Z1 = ZoneOffset.ofHours(-8); + + private static final ObjectReader READER_UTC_DEFAULT = newMapperBuilder() + .defaultTimeZone(TimeZone.getTimeZone(UTC)) + .build() + .readerFor(ZonedDateTime.class); + + private static final ObjectReader READER_Z1_DEFAULT = newMapperBuilder() + .defaultTimeZone(TimeZone.getTimeZone(Z1)) + .build() + .readerFor(ZonedDateTime.class); + + private static IonObjectMapper.Builder newMapperBuilder() { + return IonObjectMapper.builder() + .addModule(new IonJavaTimeModule()); + } + + /* + ********************************************************************** + * Deserialization from decimal value (seconds with fractions) + ********************************************************************** + */ + + @Test + public void testDeserializationAsFloat01() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(0L), UTC); + ZonedDateTime actual = READER_UTC_DEFAULT.readValue("0.000000"); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsFloat01NonUTCDefault() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(0L), Z1); + ZonedDateTime actual = READER_Z1_DEFAULT.readValue("0.000000"); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsFloat02() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), UTC); + ZonedDateTime actual = READER_UTC_DEFAULT.readValue("123456789.183917322"); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsFloat02NonUTCDefault() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z1); + ZonedDateTime actual = READER_Z1_DEFAULT.readValue("123456789.183917322"); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsFloat03() throws Exception { + Instant now = Instant.now(); + ZonedDateTime expected = ZonedDateTime.ofInstant(now, UTC); + ZonedDateTime actual = READER_UTC_DEFAULT.readValue(TimestampUtils.getFractionalSeconds(now).toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsFloat03NonUTCDefault() throws Exception { + Instant now = Instant.now(); + ZonedDateTime expected = ZonedDateTime.ofInstant(now, Z1); + ZonedDateTime actual = READER_Z1_DEFAULT.readValue(TimestampUtils.getFractionalSeconds(now).toString()); + assertEquals("The value is not correct.", expected, actual); + } + + /* + ********************************************************************** + * Deserialization from int value (milliseconds) + ********************************************************************** + */ + + @Test + public void testDeserializationAsInt01Nanoseconds() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(0L), UTC); + ZonedDateTime actual = READER_UTC_DEFAULT + .with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("0"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt01NanosecondsNonUTCDefault() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(0L), Z1); + ZonedDateTime actual = READER_Z1_DEFAULT + .with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("0"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt02Nanoseconds() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L), UTC); + ZonedDateTime actual = READER_UTC_DEFAULT + .with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("123456789"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt02NanosecondsNonUTCDefault() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L), Z1); + ZonedDateTime actual = READER_Z1_DEFAULT + .with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("123456789"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt03Nanoseconds() throws Exception { + Instant now = Instant.now(); + + ZonedDateTime expected = ZonedDateTime.ofInstant(now, UTC).truncatedTo(ChronoUnit.SECONDS); + ZonedDateTime actual = READER_UTC_DEFAULT + .with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue(Long.toString(now.getEpochSecond())); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt03NanosecondsNonUTCDefault() throws Exception { + Instant now = Instant.now(); + + ZonedDateTime expected = ZonedDateTime.ofInstant(now, Z1).truncatedTo(ChronoUnit.SECONDS); + ZonedDateTime actual = READER_Z1_DEFAULT + .with(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue(Long.toString(now.getEpochSecond())); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt01Milliseconds() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(0L), UTC); + ZonedDateTime actual = READER_UTC_DEFAULT + .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("0"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt01MillisecondsNonUTCDefault() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(0L), Z1); + ZonedDateTime actual = READER_Z1_DEFAULT + .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("0"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt02Milliseconds() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 422000000), UTC); + ZonedDateTime actual = READER_UTC_DEFAULT + .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("123456789422"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt02MillisecondsNonUTCDefault() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 422000000), Z1); + ZonedDateTime actual = READER_Z1_DEFAULT + .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue("123456789422"); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt03Milliseconds() throws Exception { + Instant now = Instant.now(); + + ZonedDateTime expected = ZonedDateTime.ofInstant(now, UTC).truncatedTo(ChronoUnit.MILLIS); + ZonedDateTime actual = READER_UTC_DEFAULT + .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue(Long.toString(now.toEpochMilli())); + + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsInt03MillisecondsNonUTCDefault() throws Exception { + Instant now = Instant.now(); + + ZonedDateTime expected = ZonedDateTime.ofInstant(now, Z1).truncatedTo(ChronoUnit.MILLIS); + ZonedDateTime actual = READER_Z1_DEFAULT + .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue(Long.toString(now.toEpochMilli())); + + assertEquals("The value is not correct.", expected, actual); + } + + /* + ********************************************************************** + * Deserialization from Ion timestamp value + ********************************************************************** + */ + + @Test + public void testDeserializationAsIonTimestamp01() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(0L), UTC); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), expected.getOffset()); + ZonedDateTime actual = READER_UTC_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp01NonUTCTimeOffset() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(0L), Z1); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), expected.getOffset()); + ZonedDateTime actual = READER_UTC_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp02() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), UTC); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), expected.getOffset()); + ZonedDateTime actual = READER_UTC_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp02NonUTCTimeOffset() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z1); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), expected.getOffset()); + ZonedDateTime actual = READER_UTC_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp03() throws Exception { + ZonedDateTime expected = ZonedDateTime.now(UTC); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), expected.getOffset()); + ZonedDateTime actual = READER_UTC_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp03NonUTCTimeOffset() throws Exception { + ZonedDateTime expected = ZonedDateTime.now(Z1); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), expected.getOffset()); + ZonedDateTime actual = READER_UTC_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp04UnknownOffset() throws Exception { + ZonedDateTime expected = ZonedDateTime.now(UTC); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), null); + ZonedDateTime actual = READER_UTC_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationAsIonTimestamp04UnknownOffsetNonUTCDefault() throws Exception { + ZonedDateTime expected = ZonedDateTime.now(Z1); + Timestamp timestamp = TimestampUtils.toTimestamp(expected.toInstant(), null); + ZonedDateTime actual = READER_Z1_DEFAULT.readValue(timestamp.toString()); + assertEquals("The value is not correct.", expected, actual); + } + + /* + ********************************************************************** + * Deserialization from values with type info + ********************************************************************** + */ + + @Test + public void testDeserializationWithTypeInfo01() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), UTC); + + IonObjectMapper m = newMapperBuilder() + .enable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Temporal actual = m.readValue("[\"" + ZonedDateTime.class.getName() + "\",123456789.183917322]", Temporal.class); + assertTrue("The value should be an ZonedDateTime.", actual instanceof ZonedDateTime); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo01NonUTCDefault() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z1); + + IonObjectMapper m = newMapperBuilder() + .defaultTimeZone(TimeZone.getTimeZone(Z1)) + .enable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Temporal actual = m.readValue("[\"" + ZonedDateTime.class.getName() + "\",123456789.183917322]", Temporal.class); + assertTrue("The value should be an ZonedDateTime.", actual instanceof ZonedDateTime); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo02() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 0), UTC); + + IonObjectMapper m = newMapperBuilder() + .enable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Temporal actual = m.readValue("[\"" + ZonedDateTime.class.getName() + "\",123456789]", Temporal.class); + assertTrue("The value should be an ZonedDateTime.", actual instanceof ZonedDateTime); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo02NonUTCDefault() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 0), Z1); + + IonObjectMapper m = newMapperBuilder() + .defaultTimeZone(TimeZone.getTimeZone(Z1)) + .enable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Temporal actual = m.readValue("[\"" + ZonedDateTime.class.getName() + "\",123456789]", Temporal.class); + assertTrue("The value should be an ZonedDateTime.", actual instanceof ZonedDateTime); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo03() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 422000000), UTC); + + IonObjectMapper m = newMapperBuilder() + .disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Temporal actual = m.readValue("[\"" + ZonedDateTime.class.getName() + "\", 123456789422]", Temporal.class); + assertTrue("The value should be an ZonedDateTime.", actual instanceof ZonedDateTime); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo03NonUTCDefault() throws Exception { + ZonedDateTime expected = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 422000000), Z1); + + IonObjectMapper m = newMapperBuilder() + .defaultTimeZone(TimeZone.getTimeZone(Z1)) + .disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Temporal actual = m.readValue("[\"" + ZonedDateTime.class.getName() + "\", 123456789422]", Temporal.class); + assertTrue("The value should be an ZonedDateTime.", actual instanceof ZonedDateTime); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo04() throws Exception { + Instant now = Instant.now(); + ZonedDateTime expected = ZonedDateTime.ofInstant(now, UTC); + + IonObjectMapper m = newMapperBuilder() + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Timestamp timestamp = TimestampUtils.toTimestamp(now, ZoneOffset.UTC); + Temporal actual = m.readValue("[\"" + ZonedDateTime.class.getName() + "\"," + timestamp.toString() + "]", + Temporal.class); + + assertTrue("The value should be an ZonedDateTime.", actual instanceof ZonedDateTime); + assertEquals("The value is not correct.", expected, actual); + } + + @Test + public void testDeserializationWithTypeInfo04NonUTCOffset() throws Exception { + Instant now = Instant.now(); + ZonedDateTime expected = ZonedDateTime.ofInstant(now, Z1); + + IonObjectMapper m = newMapperBuilder() + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .build(); + + Timestamp timestamp = TimestampUtils.toTimestamp(now, expected.getOffset()); + Temporal actual = m.readValue("[\"" + ZonedDateTime.class.getName() + "\"," + timestamp.toString() + "]", + Temporal.class); + + assertTrue("The value should be an ZonedDateTime.", actual instanceof ZonedDateTime); + assertEquals("The value is not correct.", expected, actual); + } +} diff --git a/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampZonedDateTimeSerializerTest.java b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampZonedDateTimeSerializerTest.java new file mode 100644 index 000000000..d50e369fa --- /dev/null +++ b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampZonedDateTimeSerializerTest.java @@ -0,0 +1,176 @@ +package com.fasterxml.jackson.dataformat.ion.jsr310; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.Temporal; + +import org.junit.Test; + +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.ion.IonObjectMapper; + +public class IonTimestampZonedDateTimeSerializerTest { + + private static final ZoneId Z1 = ZoneId.of("America/Chicago"); + private static final ZoneId Z2 = ZoneId.of("America/Anchorage"); + private static final ZoneId Z3 = ZoneId.of("America/Los_Angeles"); + + private IonObjectMapper.Builder newMapperBuilder() { + return IonObjectMapper.builder() + .addModule(new IonJavaTimeModule()); + } + + @Test + public void testSerializationAsTimestamp01Nanoseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + ZonedDateTime date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(0L), Z1); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", "0.", value); + } + + @Test + public void testSerializationAsTimestamp01Milliseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + ZonedDateTime date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(0L), Z1); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", "0", value); + } + + @Test + public void testSerializationAsTimestamp02Nanoseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + ZonedDateTime date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z2); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", "123456789.183917322", value); + } + + @Test + public void testSerializationAsTimestamp02Milliseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + ZonedDateTime date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z2); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", "123456789183", value); + } + + @Test + public void testSerializationAsTimestamp03Nanoseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + ZonedDateTime date = ZonedDateTime.now(Z3); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", TimestampUtils.getFractionalSeconds(date.toInstant()).toString(), value); + } + + @Test + public void testSerializationAsTimestamp03Milliseconds() throws Exception { + IonObjectMapper mapper = newMapperBuilder() + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + ZonedDateTime date = ZonedDateTime.now(Z3); + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", Long.toString(date.toInstant().toEpochMilli()), value); + } + + @Test + public void testSerializationAsString01() throws Exception { + ZonedDateTime date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(0L), Z1); + IonObjectMapper mapper = newMapperBuilder() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", + TimestampUtils.toTimestamp(date.toInstant(), date.getOffset()).toString(), value); + } + + @Test + public void testSerializationAsString02() throws Exception { + ZonedDateTime date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z2); + IonObjectMapper mapper = newMapperBuilder() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", + TimestampUtils.toTimestamp(date.toInstant(), date.getOffset()).toString(), value); + } + + @Test + public void testSerializationAsString03() throws Exception { + ZonedDateTime date = ZonedDateTime.now(Z3); + IonObjectMapper mapper = newMapperBuilder() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", + TimestampUtils.toTimestamp(date.toInstant(), date.getOffset()).toString(), value); + } + + @Test + public void testSerializationWithTypeInfo01() throws Exception { + ZonedDateTime date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z2); + IonObjectMapper mapper = newMapperBuilder() + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", + "'" + ZonedDateTime.class.getName() + "'::123456789.183917322", value); + } + + @Test + public void testSerializationWithTypeInfo02() throws Exception { + ZonedDateTime date = ZonedDateTime.ofInstant(Instant.ofEpochSecond(123456789L, 183917322), Z2); + IonObjectMapper mapper = newMapperBuilder() + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS) + .build(); + + String value = mapper.writeValueAsString(date); + assertEquals("The value is not correct.", + "'" + ZonedDateTime.class.getName() + "'::123456789183", value); + } + + @Test + public void testSerializationWithTypeInfo03() throws Exception { + ZonedDateTime date = ZonedDateTime.now(Z3); + IonObjectMapper mapper = newMapperBuilder() + .addMixIn(Temporal.class, MockObjectConfiguration.class) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + String value = mapper.writeValueAsString(date); + assertNotNull("The value should not be null.", value); + assertEquals("The value is not correct.","'" + ZonedDateTime.class.getName() + "'::" + + TimestampUtils.toTimestamp(date.toInstant(), date.getOffset()).toString(), value); + } +} diff --git a/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/MockObjectConfiguration.java b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/MockObjectConfiguration.java new file mode 100644 index 000000000..202e294fc --- /dev/null +++ b/ion/src/test/java/com/fasterxml/jackson/dataformat/ion/jsr310/MockObjectConfiguration.java @@ -0,0 +1,23 @@ +/* + * Copyright 2013 FasterXML.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package com.fasterxml.jackson.dataformat.ion.jsr310; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.EXTERNAL_PROPERTY) +public interface MockObjectConfiguration { +} From 3201e41f5b5ae661c63efb356f946d24d97c8395 Mon Sep 17 00:00:00 2001 From: liedtkem Date: Tue, 30 Jun 2020 19:09:30 -0700 Subject: [PATCH 2/2] Remove tabs --- .../ion/jsr310/IonTimestampInstantSerializer.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantSerializer.java b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantSerializer.java index 9fcd852cc..1e7d123c4 100644 --- a/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantSerializer.java +++ b/ion/src/main/java/com/fasterxml/jackson/dataformat/ion/jsr310/IonTimestampInstantSerializer.java @@ -35,13 +35,13 @@ public class IonTimestampInstantSerializer extends StdScalar public static final IonTimestampInstantSerializer INSTANT = new IonTimestampInstantSerializer<>(Instant.class, - Function.identity(), + Function.identity(), (instant) -> ZoneOffset.UTC, (instant, zoneId) -> instant.atZone(zoneId).getOffset()); public static final IonTimestampInstantSerializer OFFSET_DATE_TIME = new IonTimestampInstantSerializer<>(OffsetDateTime.class, - OffsetDateTime::toInstant, + OffsetDateTime::toInstant, OffsetDateTime::getOffset, (offsetDateTime, zoneId) -> offsetDateTime.atZoneSameInstant(zoneId).getOffset()); @@ -51,9 +51,9 @@ public class IonTimestampInstantSerializer extends StdScalar */ public static final IonTimestampInstantSerializer ZONED_DATE_TIME = new IonTimestampInstantSerializer<>(ZonedDateTime.class, - ZonedDateTime::toInstant, - ZonedDateTime::getOffset, - (zonedDateTime, zoneId) -> zonedDateTime.withZoneSameInstant(zoneId).getOffset()); + ZonedDateTime::toInstant, + ZonedDateTime::getOffset, + (zonedDateTime, zoneId) -> zonedDateTime.withZoneSameInstant(zoneId).getOffset()); private final Function getInstant; private final Function getOffset;