From 79d03791f1589cfa5929ebf85d75c3ab385051d6 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 11 Nov 2023 19:00:40 -0800 Subject: [PATCH] Initial part of #281: add scaffolding for configurable zoneid normalization (#284) --- .../datatype/jsr310/JavaTimeFeature.java | 9 ++++-- .../jsr310/deser/InstantDeserializer.java | 30 +++++++++++++++---- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeFeature.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeFeature.java index 81dde0b8..6ac8c53c 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeFeature.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeFeature.java @@ -10,9 +10,14 @@ public enum JavaTimeFeature implements JacksonFeature { /** - * Placeholder + * Feature that determines whether {@link java.time.ZoneId} is normalized + * (via call to {@code java.time.ZoneId#normalized()}) when deserializing + * types like {@link java.time.ZonedDateTime}. + *

+ * Default setting is enabled, for backwards-compatibility with + * Jackson 2.15. */ - BOGUS(false); + NORMALIZE_DESERIALIZED_ZONE_ID(true); /** * Whether feature is enabled or disabled by default. 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 99ef65ea..d1566c82 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 @@ -67,7 +67,8 @@ public class InstantDeserializer a -> Instant.ofEpochMilli(a.value), a -> Instant.ofEpochSecond(a.integer, a.fraction), null, - true // yes, replace zero offset with Z + true, // yes, replace zero offset with Z + true // default: yes, normalize ZoneId ); public static final InstantDeserializer OFFSET_DATE_TIME = new InstantDeserializer<>( @@ -76,7 +77,8 @@ public class InstantDeserializer a -> OffsetDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId), 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, // yes, replace zero offset with Z + true // default: yes, normalize ZoneId ); public static final InstantDeserializer ZONED_DATE_TIME = new InstantDeserializer<>( @@ -85,7 +87,8 @@ public class InstantDeserializer a -> ZonedDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId), a -> ZonedDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId), ZonedDateTime::withZoneSameInstant, - false // keep zero offset and Z separate since zones explicitly supported + false, // keep zero offset and Z separate since zones explicitly supported + true // default: yes, normalize ZoneId ); protected final Function fromMilliseconds; @@ -119,13 +122,21 @@ public class InstantDeserializer */ protected final Boolean _readTimestampsAsNanosOverride; + /** + * Flag set from + * {@link com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature#NORMALIZE_DESERIALIZED_ZONE_ID} to + * determine whether {@link ZoneId} is to be normalized during deserialization. + */ + protected final boolean _normalizeZoneId; + protected InstantDeserializer(Class supportedType, DateTimeFormatter formatter, Function parsedToValue, Function fromMilliseconds, Function fromNanoseconds, BiFunction adjust, - boolean replaceZeroOffsetAsZ) + boolean replaceZeroOffsetAsZ, + boolean normalizeZoneId) { super(supportedType, formatter); this.parsedToValue = parsedToValue; @@ -135,6 +146,7 @@ protected InstantDeserializer(Class supportedType, this.replaceZeroOffsetAsZ = replaceZeroOffsetAsZ; this._adjustToContextTZOverride = null; this._readTimestampsAsNanosOverride = null; + _normalizeZoneId = normalizeZoneId; } @SuppressWarnings("unchecked") @@ -148,6 +160,7 @@ protected InstantDeserializer(InstantDeserializer base, DateTimeFormatter f) replaceZeroOffsetAsZ = (_formatter == DateTimeFormatter.ISO_INSTANT); _adjustToContextTZOverride = base._adjustToContextTZOverride; _readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride; + _normalizeZoneId = base._normalizeZoneId; } @SuppressWarnings("unchecked") @@ -161,6 +174,7 @@ protected InstantDeserializer(InstantDeserializer base, Boolean adjustToConte replaceZeroOffsetAsZ = base.replaceZeroOffsetAsZ; _adjustToContextTZOverride = adjustToContextTimezoneOverride; _readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride; + _normalizeZoneId = base._normalizeZoneId; } @SuppressWarnings("unchecked") @@ -174,6 +188,7 @@ protected InstantDeserializer(InstantDeserializer base, DateTimeFormatter f, replaceZeroOffsetAsZ = (_formatter == DateTimeFormatter.ISO_INSTANT); _adjustToContextTZOverride = base._adjustToContextTZOverride; _readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride; + _normalizeZoneId = base._normalizeZoneId; } /** @@ -194,6 +209,7 @@ protected InstantDeserializer(InstantDeserializer base, replaceZeroOffsetAsZ = base.replaceZeroOffsetAsZ; _adjustToContextTZOverride = adjustToContextTimezoneOverride; _readTimestampsAsNanosOverride = readTimestampsAsNanosOverride; + _normalizeZoneId = base._normalizeZoneId; } @Override @@ -364,7 +380,11 @@ private ZoneId getZone(DeserializationContext context) // Instants are always in UTC, so don't waste compute cycles // Normalizing the zone to prevent discrepancies. // See https://github.com/FasterXML/jackson-modules-java8/pull/267 for details - return (_valueClass == Instant.class) ? null : context.getTimeZone().toZoneId().normalized(); + if (_valueClass == Instant.class) { + return null; + } + ZoneId zoneId = context.getTimeZone().toZoneId(); + return _normalizeZoneId ? zoneId.normalized() : zoneId; } private String replaceZeroOffsetAsZIfNecessary(String text)