diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.java index a862eeba..f95eb87a 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.java @@ -121,9 +121,13 @@ public void setupModule(SetupContext context) { SimpleDeserializers desers = new SimpleDeserializers(); // // Instant variants: - desers.addDeserializer(Instant.class, InstantDeserializer.INSTANT); - desers.addDeserializer(OffsetDateTime.class, InstantDeserializer.OFFSET_DATE_TIME); - desers.addDeserializer(ZonedDateTime.class, InstantDeserializer.ZONED_DATE_TIME); + desers.addDeserializer(Instant.class, + InstantDeserializer.INSTANT.withFeatures(_features)); + desers.addDeserializer(OffsetDateTime.class, + InstantDeserializer.OFFSET_DATE_TIME.withFeatures(_features)); + desers.addDeserializer(ZonedDateTime.class, + InstantDeserializer.ZONED_DATE_TIME.withFeatures(_features)); + // // Other deserializers desers.addDeserializer(Duration.class, DurationDeserializer.INSTANCE); desers.addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE); diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java index d1566c82..1d1f4dfb 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java @@ -21,10 +21,12 @@ import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.JsonTokenId; import com.fasterxml.jackson.core.io.NumberInput; +import com.fasterxml.jackson.core.util.JacksonFeatureSet; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.datatype.jsr310.DecimalUtils; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature; import java.io.IOException; import java.math.BigDecimal; @@ -54,6 +56,8 @@ public class InstantDeserializer { private static final long serialVersionUID = 1L; + private final static boolean DEFAULT_NORMALIZE_ZONE_ID = JavaTimeFeature.NORMALIZE_DESERIALIZED_ZONE_ID.enabledByDefault(); + /** * Constants used to check if ISO 8601 time string is colonless. See [jackson-modules-java8#131] * @@ -68,7 +72,7 @@ public class InstantDeserializer a -> Instant.ofEpochSecond(a.integer, a.fraction), null, true, // yes, replace zero offset with Z - true // default: yes, normalize ZoneId + DEFAULT_NORMALIZE_ZONE_ID ); public static final InstantDeserializer OFFSET_DATE_TIME = new InstantDeserializer<>( @@ -78,7 +82,7 @@ public class InstantDeserializer a -> OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId), (d, z) -> (d.isEqual(OffsetDateTime.MIN) || d.isEqual(OffsetDateTime.MAX) ? d : d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime()))), true, // yes, replace zero offset with Z - true // default: yes, normalize ZoneId + DEFAULT_NORMALIZE_ZONE_ID ); public static final InstantDeserializer ZONED_DATE_TIME = new InstantDeserializer<>( @@ -88,7 +92,7 @@ public class InstantDeserializer a -> ZonedDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId), ZonedDateTime::withZoneSameInstant, false, // keep zero offset and Z separate since zones explicitly supported - true // default: yes, normalize ZoneId + DEFAULT_NORMALIZE_ZONE_ID ); protected final Function fromMilliseconds; @@ -212,6 +216,26 @@ protected InstantDeserializer(InstantDeserializer base, _normalizeZoneId = base._normalizeZoneId; } + /** + * @since 2.16 + */ + @SuppressWarnings("unchecked") + protected InstantDeserializer(InstantDeserializer base, + JacksonFeatureSet features) + { + super((Class) base.handledType(), base._formatter); + parsedToValue = base.parsedToValue; + fromMilliseconds = base.fromMilliseconds; + fromNanoseconds = base.fromNanoseconds; + adjust = base.adjust; + replaceZeroOffsetAsZ = base.replaceZeroOffsetAsZ; + _adjustToContextTZOverride = base._adjustToContextTZOverride; + _readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride; + + _normalizeZoneId = features.isEnabled(JavaTimeFeature.NORMALIZE_DESERIALIZED_ZONE_ID); + + } + @Override protected InstantDeserializer withDateFormat(DateTimeFormatter dtf) { if (dtf == _formatter) { @@ -225,6 +249,14 @@ protected InstantDeserializer withLeniency(Boolean leniency) { return new InstantDeserializer<>(this, _formatter, leniency); } + // @since 2.16 + public InstantDeserializer withFeatures(JacksonFeatureSet features) { + if (_normalizeZoneId == features.isEnabled(JavaTimeFeature.NORMALIZE_DESERIALIZED_ZONE_ID)) { + return this; + } + return new InstantDeserializer<>(this, features); + } + @SuppressWarnings("unchecked") @Override // @since 2.12.1 protected JSR310DateTimeDeserializerBase _withFormatOverrides(DeserializationContext ctxt, diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZonedDateTimeDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZonedDateTimeDeserTest.java index 03e3d0a8..72dc2206 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZonedDateTimeDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZonedDateTimeDeserTest.java @@ -9,6 +9,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase; import org.junit.Test; @@ -21,6 +24,7 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.Map; +import java.util.TimeZone; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -29,6 +33,12 @@ public class ZonedDateTimeDeserTest extends ModuleTestBase { private final ObjectReader READER = newMapper().readerFor(ZonedDateTime.class); + + private final ObjectReader READER_NON_NORMALIZED_ZONEID = JsonMapper.builder() + .addModule(new JavaTimeModule().disable(JavaTimeFeature.NORMALIZE_DESERIALIZED_ZONE_ID)) + .build() + .readerFor(ZonedDateTime.class); + private final TypeReference> MAP_TYPE_REF = new TypeReference>() { }; static class WrapperWithFeatures { @@ -57,13 +67,24 @@ public WrapperWithReadTimestampsAsNanosEnabled() { } } @Test - public void testDeserializationAsString01() throws Exception + public void testDeserFromString() throws Exception { assertEquals("The value is not correct.", ZonedDateTime.of(2000, 1, 1, 12, 0, 0, 0, ZoneOffset.UTC), READER.readValue(q("2000-01-01T12:00Z"))); } + // [modules-java#281] + @Test + public void testDeserFromStringNoZoneIdNormalization() throws Exception + { + // 11-Nov-2023, tatu: Not sure this is great test but... does show diff + // behavior with and without `JavaTimeFeature.NORMALIZE_DESERIALIZED_ZONE_ID` + assertEquals("The value is not correct.", + ZonedDateTime.of(2000, 1, 1, 12, 0, 0, 0, TimeZone.getTimeZone("UTC").toZoneId()), + READER_NON_NORMALIZED_ZONEID.readValue(q("2000-01-01T12:00Z"))); + } + @Test public void testDeserializationAsInt01() throws Exception { diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 3b32725e..b545e902 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -13,6 +13,9 @@ Modules: #272: (datetime) `JsonFormat.Feature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS` not respected when deserialising `Instant`s (fix contributed by Raman B) +#281: (datetime) Add `JavaTimeFeature.NORMALIZE_DESERIALIZED_ZONE_ID` to allow + disabling ZoneId normalization on deserialization + (requested by @indyana) 2.15.3 (12-Oct-2023) 2.15.2 (30-May-2023)