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..1e7d123c4
--- /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 {
+}