diff --git a/datetime/README.md b/datetime/README.md index 70dc6271..3892400e 100644 --- a/datetime/README.md +++ b/datetime/README.md @@ -35,7 +35,7 @@ more ambiguous integer types are read as fractional seconds without a decimal po For TimeZone handling, `ADJUST_DATES_TO_CONTEXT_TIME_ZONE` (default: true) specifies whether the context provided by `java.time.TimeZone` 'SerializedProvider#getTimeZone()' should be used to adjust Date/Time values on deserialization, even if the value itself -contains timezone information. If the value is `OffsetDateTime.MIN` or `OffsetDateTime.MAX`, the Date/Time value will not be adjusted. If disabled, it will only be used if the value itself does not contain any TimeZone information. +contains timezone information. The resultant ZoneId will be [normalized](https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html#normalized--) where applicable. If the value is `OffsetDateTime.MIN` or `OffsetDateTime.MAX`, the Date/Time value will not be adjusted. If disabled, it will only be used if the value itself does not contain any TimeZone information. Finally, there are two features that apply to array handling. `UNWRAP_SINGLE_VALUE_ARRAYS` (default: false) allows auto-conversion from single-element arrays to non-JSON-array values. If the JSON value contains more than one element in the array, deserialization will still fail. `ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT` (default: false) determines whether empty Array value ("[ ]" in JSON) is accepted 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 0daf2f04..f9318472 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 @@ -324,7 +324,9 @@ protected T _fromDecimal(DeserializationContext context, BigDecimal value) private ZoneId getZone(DeserializationContext context) { // Instants are always in UTC, so don't waste compute cycles - return (_valueClass == Instant.class) ? null : context.getTimeZone().toZoneId(); + // 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(); } private String replaceZeroOffsetAsZIfNecessary(String text) diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java index 9382c393..6d98821a 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java @@ -389,9 +389,9 @@ public void testCustomPatternWithAnnotations02() throws Exception { //Test date is pushed one year after start of the epoch just to avoid possible issues with UTC-X TZs which could //push the instant before tha start of the epoch - final Instant instant = ZonedDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.of("UTC")).plusYears(1).toInstant(); + final Instant instant = ZonedDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneOffset.UTC).plusYears(1).toInstant(); final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(CUSTOM_PATTERN); - final String valueInUTC = formatter.withZone(ZoneId.of("UTC")).format(instant); + final String valueInUTC = formatter.withZone(ZoneOffset.UTC).format(instant); final WrapperWithCustomPattern input = new WrapperWithCustomPattern(instant); String json = MAPPER.writeValueAsString(input); 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 a668045f..fae64f86 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 @@ -16,6 +16,7 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.Map; @@ -37,10 +38,34 @@ static class WrapperWithFeatures { public void testDeserializationAsString01() throws Exception { assertEquals("The value is not correct.", - ZonedDateTime.of(2000, 1, 1, 12, 0, 0, 0, ZoneId.of("UTC")), + ZonedDateTime.of(2000, 1, 1, 12, 0, 0, 0, ZoneOffset.UTC), READER.readValue(q("2000-01-01T12:00Z"))); } + @Test + public void testDeserializationComparedToStandard() throws Throwable + { + String inputString = "2021-02-01T19:49:04.0513486Z"; + + assertEquals("The value is not correct.", + DateTimeFormatter.ISO_ZONED_DATE_TIME.parse(inputString, ZonedDateTime::from), + READER.readValue(q(inputString))); + } + + @Test + public void testDeserializationComparedToStandard2() throws Throwable + { + String inputString = "2021-02-01T19:49:04.0513486Z[UTC]"; + + ZonedDateTime converted = newMapper() + .configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false) + .readerFor(ZonedDateTime.class).readValue(q(inputString)); + + assertEquals("The value is not correct.", + DateTimeFormatter.ISO_ZONED_DATE_TIME.parse(inputString, ZonedDateTime::from), + converted); + } + @Test public void testBadDeserializationAsString01() throws Throwable { @@ -92,7 +117,7 @@ public void testDeserializationAsArrayEnabled() throws Throwable .configure(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS, true) .readerFor(ZonedDateTime.class).readValue(a2q(json)); assertEquals("The value is not correct.", - ZonedDateTime.of(2000, 1, 1, 12, 0, 0, 0, ZoneId.of("UTC")), + ZonedDateTime.of(2000, 1, 1, 12, 0, 0, 0, ZoneOffset.UTC), value); } diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/old/TestZonedDateTimeSerialization.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/old/TestZonedDateTimeSerialization.java index 90baac0b..188092fa 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/old/TestZonedDateTimeSerialization.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/old/TestZonedDateTimeSerialization.java @@ -22,6 +22,7 @@ import java.time.Instant; import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; @@ -44,7 +45,7 @@ public class TestZonedDateTimeSerialization extends ModuleTestBase { private static final ZoneId Z3 = ZoneId.of("America/Los_Angeles"); - private static final ZoneId UTC = ZoneId.of("UTC"); + private static final ZoneId UTC = ZoneOffset.UTC; private static final ZoneId DEFAULT_TZ = UTC; @@ -254,7 +255,7 @@ public void testDeserializationAsFloat01WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -279,7 +280,7 @@ public void testDeserializationAsFloat02WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -308,7 +309,7 @@ public void testDeserializationAsFloat03WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -335,7 +336,7 @@ public void testDeserializationAsInt01NanosecondsWithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -362,7 +363,7 @@ public void testDeserializationAsInt01MillisecondsWithTimeZone() throws Exceptio assertNotNull("The value should not be null.", value); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -389,7 +390,7 @@ public void testDeserializationAsInt02NanosecondsWithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -416,7 +417,7 @@ public void testDeserializationAsInt02MillisecondsWithTimeZone() throws Exceptio assertNotNull("The value should not be null.", value); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -445,7 +446,7 @@ public void testDeserializationAsInt03NanosecondsWithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -476,7 +477,7 @@ public void testDeserializationAsInt03MillisecondsWithTimeZone() throws Exceptio assertNotNull("The value should not be null.", value); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -503,7 +504,7 @@ public void testDeserializationAsString01WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -544,7 +545,7 @@ public void testDeserializationAsString02WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -585,7 +586,7 @@ public void testDeserializationAsString03WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -632,7 +633,7 @@ public void testDeserializationWithTypeInfo01WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertTrue("The value should be an ZonedDateTime.", value instanceof ZonedDateTime); assertIsEqual(date, (ZonedDateTime) value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), ((ZonedDateTime) value).getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), ((ZonedDateTime) value).getZone()); } @Test @@ -667,7 +668,7 @@ public void testDeserializationWithTypeInfo02WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertTrue("The value should be an ZonedDateTime.", value instanceof ZonedDateTime); assertIsEqual(date, (ZonedDateTime) value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), ((ZonedDateTime) value).getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), ((ZonedDateTime) value).getZone()); } @Test @@ -702,7 +703,7 @@ public void testDeserializationWithTypeInfo03WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertTrue("The value should be an ZonedDateTime.", value instanceof ZonedDateTime); assertIsEqual(date, (ZonedDateTime) value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), ((ZonedDateTime) value).getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), ((ZonedDateTime) value).getZone()); } @Test @@ -737,7 +738,7 @@ public void testDeserializationWithTypeInfo04WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertTrue("The value should be an ZonedDateTime.", value instanceof ZonedDateTime); assertIsEqual(date, (ZonedDateTime) value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), ((ZonedDateTime) value).getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), ((ZonedDateTime) value).getZone()); } @Test diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ser/ZonedDateTimeSerTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ser/ZonedDateTimeSerTest.java index 98169ef3..fece304c 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ser/ZonedDateTimeSerTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ser/ZonedDateTimeSerTest.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import java.time.Instant; import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoField; @@ -55,7 +56,7 @@ public class ZonedDateTimeSerTest private static final ZoneId Z3 = ZoneId.of("America/Los_Angeles"); - private static final ZoneId UTC = ZoneId.of("UTC"); + private static final ZoneId UTC = ZoneOffset.UTC; private static final ZoneId DEFAULT_TZ = UTC; @@ -418,7 +419,7 @@ public void testDeserializationAsFloat01WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -444,7 +445,7 @@ public void testDeserializationAsFloat02WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -467,7 +468,7 @@ public void testDeserializationAsFloat03WithTimeZone() throws Exception .readValue(DecimalUtils.toDecimal(date.toEpochSecond(), date.getNano())); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -492,7 +493,7 @@ public void testDeserializationAsInt01NanosecondsWithTimeZone() throws Exception .readValue("0"); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -517,7 +518,7 @@ public void testDeserializationAsInt01MillisecondsWithTimeZone() throws Exceptio .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) .readValue("0"); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -540,7 +541,7 @@ public void testDeserializationAsInt02NanosecondsWithTimeZone() throws Exception .with(TimeZone.getDefault()) .readValue("123456789"); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -563,7 +564,7 @@ public void testDeserializationAsInt02MillisecondsWithTimeZone() throws Exceptio .without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) .readValue("123456789422"); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -590,7 +591,7 @@ public void testDeserializationAsInt03NanosecondsWithTimeZone() throws Exception .with(TimeZone.getDefault()) .readValue(Long.toString(date.toEpochSecond()), ZonedDateTime.class); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -617,7 +618,7 @@ public void testDeserializationAsInt03MillisecondsWithTimeZone() throws Exceptio .with(TimeZone.getDefault()) .readValue(Long.toString(date.toInstant().toEpochMilli())); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -640,7 +641,7 @@ public void testDeserializationAsString01WithTimeZone() throws Exception .with(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE) .readValue('"' + FORMATTER.format(date) + '"'); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -682,7 +683,7 @@ public void testDeserializationAsString02WithTimeZone() throws Exception .with(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE) .readValue('"' + FORMATTER.format(date) + '"'); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -724,7 +725,7 @@ public void testDeserializationAsString03WithTimeZone() throws Exception .with(TimeZone.getDefault()) .readValue('"' + FORMATTER.format(date) + '"'); assertIsEqual(date, value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone()); } @Test @@ -779,7 +780,7 @@ public void testDeserializationWithTypeInfo01WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertTrue("The value should be an ZonedDateTime.", value instanceof ZonedDateTime); assertIsEqual(date, (ZonedDateTime) value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), ((ZonedDateTime) value).getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), ((ZonedDateTime) value).getZone()); } @Test @@ -815,7 +816,7 @@ public void testDeserializationWithTypeInfo02WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertTrue("The value should be an ZonedDateTime.", value instanceof ZonedDateTime); assertIsEqual(date, (ZonedDateTime) value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), ((ZonedDateTime) value).getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), ((ZonedDateTime) value).getZone()); } @Test @@ -851,7 +852,7 @@ public void testDeserializationWithTypeInfo03WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertTrue("The value should be an ZonedDateTime.", value instanceof ZonedDateTime); assertIsEqual(date, (ZonedDateTime) value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), ((ZonedDateTime) value).getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), ((ZonedDateTime) value).getZone()); } @Test @@ -889,7 +890,7 @@ public void testDeserializationWithTypeInfo04WithTimeZone() throws Exception assertNotNull("The value should not be null.", value); assertTrue("The value should be an ZonedDateTime.", value instanceof ZonedDateTime); assertIsEqual(date, (ZonedDateTime) value); - assertEquals("The time zone is not correct.", ZoneId.systemDefault(), ((ZonedDateTime) value).getZone()); + assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), ((ZonedDateTime) value).getZone()); } @Test