Skip to content

Commit

Permalink
Normalize zone id during ZonedDateTime deserialization (#267)
Browse files Browse the repository at this point in the history
  • Loading branch information
dscalzi authored Feb 21, 2023
1 parent 1851baf commit a9079f3
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 40 deletions.
2 changes: 1 addition & 1 deletion datetime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
{
Expand Down Expand Up @@ -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);

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit a9079f3

Please sign in to comment.