Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Normalize zone id during ZonedDateTime deserialization #267

Merged
merged 6 commits into from
Feb 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good, I like references like this!

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