diff --git a/datetime/src/main/java/tools/jackson/datatype/jsr310/JavaTimeFeature.java b/datetime/src/main/java/tools/jackson/datatype/jsr310/JavaTimeFeature.java
index 42daac42..c2fc4473 100644
--- a/datetime/src/main/java/tools/jackson/datatype/jsr310/JavaTimeFeature.java
+++ b/datetime/src/main/java/tools/jackson/datatype/jsr310/JavaTimeFeature.java
@@ -19,6 +19,19 @@ public enum JavaTimeFeature implements JacksonFeature
*/
NORMALIZE_DESERIALIZED_ZONE_ID(true),
+ /**
+ * Feature that determines whether the {@link java.util.TimeZone} of the
+ * {@link tools.jackson.databind.DeserializationContext} is used
+ * when leniently deserializing {@link java.time.LocalDate} or
+ * {@link java.time.LocalDateTime} from the UTC/ISO instant format.
+ *
+ * Default setting is disabled, for backwards-compatibility with
+ * Jackson 2.18.
+ *
+ * @since 2.19
+ */
+ USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING(false),
+
/**
* Feature that controls whether stringified numbers (Strings that without
* quotes would be legal JSON Numbers) may be interpreted as
diff --git a/datetime/src/main/java/tools/jackson/datatype/jsr310/JavaTimeModule.java b/datetime/src/main/java/tools/jackson/datatype/jsr310/JavaTimeModule.java
index e22dc897..40fab5c6 100644
--- a/datetime/src/main/java/tools/jackson/datatype/jsr310/JavaTimeModule.java
+++ b/datetime/src/main/java/tools/jackson/datatype/jsr310/JavaTimeModule.java
@@ -124,8 +124,10 @@ public void setupModule(SetupContext context) {
// // Other deserializers
.addDeserializer(Duration.class, DurationDeserializer.INSTANCE)
- .addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE)
- .addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE)
+ .addDeserializer(LocalDateTime.class,
+ LocalDateTimeDeserializer.INSTANCE.withFeatures(_features))
+ .addDeserializer(LocalDate.class,
+ LocalDateDeserializer.INSTANCE.withFeatures(_features))
.addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE)
.addDeserializer(MonthDay.class, MonthDayDeserializer.INSTANCE)
.addDeserializer(OffsetTime.class, OffsetTimeDeserializer.INSTANCE)
diff --git a/datetime/src/main/java/tools/jackson/datatype/jsr310/deser/LocalDateDeserializer.java b/datetime/src/main/java/tools/jackson/datatype/jsr310/deser/LocalDateDeserializer.java
index 86844062..c641e8db 100644
--- a/datetime/src/main/java/tools/jackson/datatype/jsr310/deser/LocalDateDeserializer.java
+++ b/datetime/src/main/java/tools/jackson/datatype/jsr310/deser/LocalDateDeserializer.java
@@ -17,12 +17,14 @@
package tools.jackson.datatype.jsr310.deser;
import java.time.DateTimeException;
+import java.time.Instant;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import com.fasterxml.jackson.annotation.JsonFormat;
import tools.jackson.core.*;
+import tools.jackson.core.util.JacksonFeatureSet;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.DeserializationFeature;
@@ -30,6 +32,8 @@
import tools.jackson.databind.cfg.CoercionAction;
import tools.jackson.databind.cfg.CoercionInputShape;
+import tools.jackson.datatype.jsr310.JavaTimeFeature;
+
/**
* Deserializer for Java 8 temporal {@link LocalDate}s.
*
@@ -37,37 +41,52 @@
*/
public class LocalDateDeserializer extends JSR310DateTimeDeserializerBase
{
+ private final static boolean DEFAULT_USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING
+ = JavaTimeFeature.USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING.enabledByDefault();
+
private static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE;
public static final LocalDateDeserializer INSTANCE = new LocalDateDeserializer();
+ /**
+ * Flag set from
+ * {@link tools.jackson.datatype.jsr310.JavaTimeFeature#USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING}
+ * to determine whether the {@link java.util.TimeZone} of the
+ * {@link tools.jackson.databind.DeserializationContext} is used
+ * when leniently deserializing from the UTC/ISO instant format.
+ */
+ protected final boolean _useTimeZoneForLenientDateParsing;
+
protected LocalDateDeserializer() {
this(DEFAULT_FORMATTER);
}
public LocalDateDeserializer(DateTimeFormatter dtf) {
super(LocalDate.class, dtf);
+ _useTimeZoneForLenientDateParsing = DEFAULT_USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING;
}
- /**
- * Since 2.10
- */
public LocalDateDeserializer(LocalDateDeserializer base, DateTimeFormatter dtf) {
super(base, dtf);
+ _useTimeZoneForLenientDateParsing = base._useTimeZoneForLenientDateParsing;
}
- /**
- * Since 2.10
- */
protected LocalDateDeserializer(LocalDateDeserializer base, Boolean leniency) {
super(base, leniency);
+ _useTimeZoneForLenientDateParsing = base._useTimeZoneForLenientDateParsing;
}
- /**
- * Since 2.11
- */
protected LocalDateDeserializer(LocalDateDeserializer base, JsonFormat.Shape shape) {
super(base, shape);
+ _useTimeZoneForLenientDateParsing = base._useTimeZoneForLenientDateParsing;
+ }
+
+ /**
+ * Since 2.19
+ */
+ protected LocalDateDeserializer(LocalDateDeserializer base, JacksonFeatureSet features) {
+ super(LocalDate.class, base._formatter);
+ _useTimeZoneForLenientDateParsing = features.isEnabled(JavaTimeFeature.USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING);
}
@Override
@@ -83,6 +102,17 @@ protected LocalDateDeserializer withLeniency(Boolean leniency) {
@Override
protected LocalDateDeserializer withShape(JsonFormat.Shape shape) { return new LocalDateDeserializer(this, shape); }
+ /**
+ * Since 2.19
+ */
+ public LocalDateDeserializer withFeatures(JacksonFeatureSet features) {
+ if (_useTimeZoneForLenientDateParsing ==
+ features.isEnabled(JavaTimeFeature.USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING)) {
+ return this;
+ }
+ return new LocalDateDeserializer(this, features);
+ }
+
@Override
public LocalDate deserialize(JsonParser parser, DeserializationContext context)
throws JacksonException
@@ -162,6 +192,9 @@ protected LocalDate _fromString(JsonParser p, DeserializationContext ctxt,
if (string.length() > 10 && string.charAt(10) == 'T') {
if (isLenient()) {
if (string.endsWith("Z")) {
+ if (_useTimeZoneForLenientDateParsing) {
+ return Instant.parse(string).atZone(ctxt.getTimeZone().toZoneId()).toLocalDate();
+ }
return LocalDate.parse(string.substring(0, string.length() - 1),
DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
diff --git a/datetime/src/main/java/tools/jackson/datatype/jsr310/deser/LocalDateTimeDeserializer.java b/datetime/src/main/java/tools/jackson/datatype/jsr310/deser/LocalDateTimeDeserializer.java
index 9602a225..7e2c80d5 100644
--- a/datetime/src/main/java/tools/jackson/datatype/jsr310/deser/LocalDateTimeDeserializer.java
+++ b/datetime/src/main/java/tools/jackson/datatype/jsr310/deser/LocalDateTimeDeserializer.java
@@ -17,20 +17,17 @@
package tools.jackson.datatype.jsr310.deser;
import java.time.DateTimeException;
+import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonFormat;
-import tools.jackson.core.JacksonException;
-import tools.jackson.core.JsonParser;
-import tools.jackson.core.JsonToken;
-import tools.jackson.core.JsonTokenId;
-import tools.jackson.databind.BeanProperty;
-import tools.jackson.databind.DeserializationContext;
-import tools.jackson.databind.DeserializationFeature;
-import tools.jackson.databind.JavaType;
+import tools.jackson.core.*;
+import tools.jackson.core.util.JacksonFeatureSet;
+import tools.jackson.databind.*;
+import tools.jackson.datatype.jsr310.JavaTimeFeature;
/**
* Deserializer for Java 8 temporal {@link LocalDateTime}s.
@@ -40,6 +37,9 @@
public class LocalDateTimeDeserializer
extends JSR310DateTimeDeserializerBase
{
+ private final static boolean DEFAULT_USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING
+ = JavaTimeFeature.USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING.enabledByDefault();
+
private static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
public static final LocalDateTimeDeserializer INSTANCE = new LocalDateTimeDeserializer();
@@ -51,6 +51,17 @@ public class LocalDateTimeDeserializer
*/
protected final Boolean _readTimestampsAsNanosOverride;
+ /**
+ * Flag set from
+ * {@link tools.jackson.datatype.jsr310.JavaTimeFeature#USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING}
+ * to determine whether the {@link java.util.TimeZone} of the
+ * {@link tools.jackson.databind.DeserializationContext} is used
+ * when leniently deserializing from the UTC/ISO instant format.
+ *
+ * @since 2.19
+ */
+ protected final boolean _useTimeZoneForLenientDateParsing;
+
protected LocalDateTimeDeserializer() { // was private before 2.12
this(DEFAULT_FORMATTER);
}
@@ -58,6 +69,7 @@ protected LocalDateTimeDeserializer() { // was private before 2.12
public LocalDateTimeDeserializer(DateTimeFormatter formatter) {
super(LocalDateTime.class, formatter);
_readTimestampsAsNanosOverride = null;
+ _useTimeZoneForLenientDateParsing = DEFAULT_USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING;
}
/**
@@ -66,6 +78,7 @@ public LocalDateTimeDeserializer(DateTimeFormatter formatter) {
protected LocalDateTimeDeserializer(LocalDateTimeDeserializer base, Boolean leniency) {
super(base, leniency);
_readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
+ _useTimeZoneForLenientDateParsing = base._useTimeZoneForLenientDateParsing;
}
/**
@@ -78,6 +91,16 @@ protected LocalDateTimeDeserializer(LocalDateTimeDeserializer base,
Boolean readTimestampsAsNanosOverride) {
super(base, leniency, formatter, shape);
_readTimestampsAsNanosOverride = readTimestampsAsNanosOverride;
+ _useTimeZoneForLenientDateParsing = base._useTimeZoneForLenientDateParsing;
+ }
+
+ /**
+ * Since 2.19
+ */
+ protected LocalDateTimeDeserializer(LocalDateTimeDeserializer base, JacksonFeatureSet features) {
+ super(LocalDateTime.class, base._formatter);
+ _readTimestampsAsNanosOverride = base._readTimestampsAsNanosOverride;
+ _useTimeZoneForLenientDateParsing = features.isEnabled(JavaTimeFeature.USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING);
}
@Override
@@ -105,6 +128,17 @@ protected JSR310DateTimeDeserializerBase> _withFormatOverrides(Deserialization
return deser;
}
+ /**
+ * Since 2.19
+ */
+ public LocalDateTimeDeserializer withFeatures(JacksonFeatureSet features) {
+ if (_useTimeZoneForLenientDateParsing ==
+ features.isEnabled(JavaTimeFeature.USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING)) {
+ return this;
+ }
+ return new LocalDateTimeDeserializer(this, features);
+ }
+
@Override
public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws JacksonException
{
@@ -195,11 +229,12 @@ protected LocalDateTime _fromString(JsonParser p, DeserializationContext ctxt,
if (_formatter == DEFAULT_FORMATTER) {
// ... only allow iff lenient mode enabled since
// JavaScript by default includes time and zone in JSON serialized Dates (UTC/ISO instant format).
- // And if so, do NOT use zoned date parsing as that can easily produce
- // incorrect answer.
if (string.length() > 10 && string.charAt(10) == 'T') {
if (string.endsWith("Z")) {
if (isLenient()) {
+ if (_useTimeZoneForLenientDateParsing) {
+ return Instant.parse(string).atZone(ctxt.getTimeZone().toZoneId()).toLocalDateTime();
+ }
return LocalDateTime.parse(string.substring(0, string.length()-1),
_formatter);
}
diff --git a/datetime/src/main/java/tools/jackson/datatype/jsr310/ser/JSR310FormattedSerializerBase.java b/datetime/src/main/java/tools/jackson/datatype/jsr310/ser/JSR310FormattedSerializerBase.java
index 23277948..20bea366 100644
--- a/datetime/src/main/java/tools/jackson/datatype/jsr310/ser/JSR310FormattedSerializerBase.java
+++ b/datetime/src/main/java/tools/jackson/datatype/jsr310/ser/JSR310FormattedSerializerBase.java
@@ -210,7 +210,6 @@ protected boolean useTimestamp(SerializationContext ctxt) {
return (_formatter == null) && useTimestampFromGlobalDefaults(ctxt);
}
- // @since 2.19
protected boolean useTimestampFromGlobalDefaults(SerializationContext ctxt) {
return (ctxt != null)
&& ctxt.isEnabled(getTimestampsFeature());
diff --git a/datetime/src/test/java/tools/jackson/datatype/jsr310/deser/LocalDateDeserTest.java b/datetime/src/test/java/tools/jackson/datatype/jsr310/deser/LocalDateDeserTest.java
index b6ce67e3..5ca91351 100644
--- a/datetime/src/test/java/tools/jackson/datatype/jsr310/deser/LocalDateDeserTest.java
+++ b/datetime/src/test/java/tools/jackson/datatype/jsr310/deser/LocalDateDeserTest.java
@@ -3,8 +3,11 @@
import java.time.*;
import java.time.temporal.Temporal;
import java.util.Map;
+import java.util.TimeZone;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import com.fasterxml.jackson.annotation.OptBoolean;
@@ -18,7 +21,9 @@
import tools.jackson.databind.*;
import tools.jackson.databind.exc.MismatchedInputException;
-
+import tools.jackson.databind.json.JsonMapper;
+import tools.jackson.datatype.jsr310.JavaTimeFeature;
+import tools.jackson.datatype.jsr310.JavaTimeModule;
import tools.jackson.datatype.jsr310.MockObjectConfiguration;
import tools.jackson.datatype.jsr310.ModuleTestBase;
@@ -28,6 +33,11 @@ public class LocalDateDeserTest extends ModuleTestBase
{
private final ObjectMapper MAPPER = newMapper();
private final ObjectReader READER = MAPPER.readerFor(LocalDate.class);
+ private final ObjectReader READER_USING_TIME_ZONE = JsonMapper.builder()
+ .addModule(new JavaTimeModule().enable(JavaTimeFeature.USE_TIME_ZONE_FOR_LENIENT_DATE_PARSING))
+ .build()
+ .readerFor(LocalDate.class);
+
private final TypeReference