Skip to content

Commit a9079f3

Browse files
authored
Normalize zone id during ZonedDateTime deserialization (#267)
1 parent 1851baf commit a9079f3

File tree

6 files changed

+69
-40
lines changed

6 files changed

+69
-40
lines changed

datetime/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ more ambiguous integer types are read as fractional seconds without a decimal po
3535

3636
For TimeZone handling, `ADJUST_DATES_TO_CONTEXT_TIME_ZONE` (default: true) specifies whether the context provided by `java.time.TimeZone`
3737
'SerializedProvider#getTimeZone()' should be used to adjust Date/Time values on deserialization, even if the value itself
38-
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.
38+
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.
3939

4040
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
4141
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

datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,9 @@ protected T _fromDecimal(DeserializationContext context, BigDecimal value)
324324
private ZoneId getZone(DeserializationContext context)
325325
{
326326
// Instants are always in UTC, so don't waste compute cycles
327-
return (_valueClass == Instant.class) ? null : context.getTimeZone().toZoneId();
327+
// Normalizing the zone to prevent discrepancies.
328+
// See https://github.com/FasterXML/jackson-modules-java8/pull/267 for details
329+
return (_valueClass == Instant.class) ? null : context.getTimeZone().toZoneId().normalized();
328330
}
329331

330332
private String replaceZeroOffsetAsZIfNecessary(String text)

datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,9 @@ public void testCustomPatternWithAnnotations02() throws Exception
389389
{
390390
//Test date is pushed one year after start of the epoch just to avoid possible issues with UTC-X TZs which could
391391
//push the instant before tha start of the epoch
392-
final Instant instant = ZonedDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.of("UTC")).plusYears(1).toInstant();
392+
final Instant instant = ZonedDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneOffset.UTC).plusYears(1).toInstant();
393393
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(CUSTOM_PATTERN);
394-
final String valueInUTC = formatter.withZone(ZoneId.of("UTC")).format(instant);
394+
final String valueInUTC = formatter.withZone(ZoneOffset.UTC).format(instant);
395395

396396
final WrapperWithCustomPattern input = new WrapperWithCustomPattern(instant);
397397
String json = MAPPER.writeValueAsString(input);

datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZonedDateTimeDeserTest.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.time.ZoneId;
1717
import java.time.ZoneOffset;
1818
import java.time.ZonedDateTime;
19+
import java.time.format.DateTimeFormatter;
1920
import java.time.format.DateTimeParseException;
2021
import java.util.Map;
2122

@@ -37,10 +38,34 @@ static class WrapperWithFeatures {
3738
public void testDeserializationAsString01() throws Exception
3839
{
3940
assertEquals("The value is not correct.",
40-
ZonedDateTime.of(2000, 1, 1, 12, 0, 0, 0, ZoneId.of("UTC")),
41+
ZonedDateTime.of(2000, 1, 1, 12, 0, 0, 0, ZoneOffset.UTC),
4142
READER.readValue(q("2000-01-01T12:00Z")));
4243
}
4344

45+
@Test
46+
public void testDeserializationComparedToStandard() throws Throwable
47+
{
48+
String inputString = "2021-02-01T19:49:04.0513486Z";
49+
50+
assertEquals("The value is not correct.",
51+
DateTimeFormatter.ISO_ZONED_DATE_TIME.parse(inputString, ZonedDateTime::from),
52+
READER.readValue(q(inputString)));
53+
}
54+
55+
@Test
56+
public void testDeserializationComparedToStandard2() throws Throwable
57+
{
58+
String inputString = "2021-02-01T19:49:04.0513486Z[UTC]";
59+
60+
ZonedDateTime converted = newMapper()
61+
.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false)
62+
.readerFor(ZonedDateTime.class).readValue(q(inputString));
63+
64+
assertEquals("The value is not correct.",
65+
DateTimeFormatter.ISO_ZONED_DATE_TIME.parse(inputString, ZonedDateTime::from),
66+
converted);
67+
}
68+
4469
@Test
4570
public void testBadDeserializationAsString01() throws Throwable
4671
{
@@ -92,7 +117,7 @@ public void testDeserializationAsArrayEnabled() throws Throwable
92117
.configure(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS, true)
93118
.readerFor(ZonedDateTime.class).readValue(a2q(json));
94119
assertEquals("The value is not correct.",
95-
ZonedDateTime.of(2000, 1, 1, 12, 0, 0, 0, ZoneId.of("UTC")),
120+
ZonedDateTime.of(2000, 1, 1, 12, 0, 0, 0, ZoneOffset.UTC),
96121
value);
97122

98123
}

datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/old/TestZonedDateTimeSerialization.java

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import java.time.Instant;
2424
import java.time.ZoneId;
25+
import java.time.ZoneOffset;
2526
import java.time.ZonedDateTime;
2627
import java.time.temporal.ChronoField;
2728
import java.time.temporal.ChronoUnit;
@@ -44,7 +45,7 @@ public class TestZonedDateTimeSerialization extends ModuleTestBase {
4445

4546
private static final ZoneId Z3 = ZoneId.of("America/Los_Angeles");
4647

47-
private static final ZoneId UTC = ZoneId.of("UTC");
48+
private static final ZoneId UTC = ZoneOffset.UTC;
4849

4950
private static final ZoneId DEFAULT_TZ = UTC;
5051

@@ -254,7 +255,7 @@ public void testDeserializationAsFloat01WithTimeZone() throws Exception
254255

255256
assertNotNull("The value should not be null.", value);
256257
assertIsEqual(date, value);
257-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone());
258+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone());
258259
}
259260

260261
@Test
@@ -279,7 +280,7 @@ public void testDeserializationAsFloat02WithTimeZone() throws Exception
279280

280281
assertNotNull("The value should not be null.", value);
281282
assertIsEqual(date, value);
282-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone());
283+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone());
283284
}
284285

285286
@Test
@@ -308,7 +309,7 @@ public void testDeserializationAsFloat03WithTimeZone() throws Exception
308309

309310
assertNotNull("The value should not be null.", value);
310311
assertIsEqual(date, value);
311-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone());
312+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone());
312313
}
313314

314315
@Test
@@ -335,7 +336,7 @@ public void testDeserializationAsInt01NanosecondsWithTimeZone() throws Exception
335336

336337
assertNotNull("The value should not be null.", value);
337338
assertIsEqual(date, value);
338-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone());
339+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone());
339340
}
340341

341342
@Test
@@ -362,7 +363,7 @@ public void testDeserializationAsInt01MillisecondsWithTimeZone() throws Exceptio
362363

363364
assertNotNull("The value should not be null.", value);
364365
assertIsEqual(date, value);
365-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone());
366+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone());
366367
}
367368

368369
@Test
@@ -389,7 +390,7 @@ public void testDeserializationAsInt02NanosecondsWithTimeZone() throws Exception
389390

390391
assertNotNull("The value should not be null.", value);
391392
assertIsEqual(date, value);
392-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone());
393+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone());
393394
}
394395

395396
@Test
@@ -416,7 +417,7 @@ public void testDeserializationAsInt02MillisecondsWithTimeZone() throws Exceptio
416417

417418
assertNotNull("The value should not be null.", value);
418419
assertIsEqual(date, value);
419-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone());
420+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone());
420421
}
421422

422423
@Test
@@ -445,7 +446,7 @@ public void testDeserializationAsInt03NanosecondsWithTimeZone() throws Exception
445446

446447
assertNotNull("The value should not be null.", value);
447448
assertIsEqual(date, value);
448-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone());
449+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone());
449450
}
450451

451452
@Test
@@ -476,7 +477,7 @@ public void testDeserializationAsInt03MillisecondsWithTimeZone() throws Exceptio
476477

477478
assertNotNull("The value should not be null.", value);
478479
assertIsEqual(date, value);
479-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone());
480+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone());
480481
}
481482

482483
@Test
@@ -503,7 +504,7 @@ public void testDeserializationAsString01WithTimeZone() throws Exception
503504

504505
assertNotNull("The value should not be null.", value);
505506
assertIsEqual(date, value);
506-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone());
507+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone());
507508
}
508509

509510
@Test
@@ -544,7 +545,7 @@ public void testDeserializationAsString02WithTimeZone() throws Exception
544545

545546
assertNotNull("The value should not be null.", value);
546547
assertIsEqual(date, value);
547-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone());
548+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone());
548549
}
549550

550551
@Test
@@ -585,7 +586,7 @@ public void testDeserializationAsString03WithTimeZone() throws Exception
585586

586587
assertNotNull("The value should not be null.", value);
587588
assertIsEqual(date, value);
588-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), value.getZone());
589+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), value.getZone());
589590
}
590591

591592
@Test
@@ -632,7 +633,7 @@ public void testDeserializationWithTypeInfo01WithTimeZone() throws Exception
632633
assertNotNull("The value should not be null.", value);
633634
assertTrue("The value should be an ZonedDateTime.", value instanceof ZonedDateTime);
634635
assertIsEqual(date, (ZonedDateTime) value);
635-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), ((ZonedDateTime) value).getZone());
636+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), ((ZonedDateTime) value).getZone());
636637
}
637638

638639
@Test
@@ -667,7 +668,7 @@ public void testDeserializationWithTypeInfo02WithTimeZone() throws Exception
667668
assertNotNull("The value should not be null.", value);
668669
assertTrue("The value should be an ZonedDateTime.", value instanceof ZonedDateTime);
669670
assertIsEqual(date, (ZonedDateTime) value);
670-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), ((ZonedDateTime) value).getZone());
671+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), ((ZonedDateTime) value).getZone());
671672
}
672673

673674
@Test
@@ -702,7 +703,7 @@ public void testDeserializationWithTypeInfo03WithTimeZone() throws Exception
702703
assertNotNull("The value should not be null.", value);
703704
assertTrue("The value should be an ZonedDateTime.", value instanceof ZonedDateTime);
704705
assertIsEqual(date, (ZonedDateTime) value);
705-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), ((ZonedDateTime) value).getZone());
706+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), ((ZonedDateTime) value).getZone());
706707
}
707708

708709
@Test
@@ -737,7 +738,7 @@ public void testDeserializationWithTypeInfo04WithTimeZone() throws Exception
737738
assertNotNull("The value should not be null.", value);
738739
assertTrue("The value should be an ZonedDateTime.", value instanceof ZonedDateTime);
739740
assertIsEqual(date, (ZonedDateTime) value);
740-
assertEquals("The time zone is not correct.", ZoneId.systemDefault(), ((ZonedDateTime) value).getZone());
741+
assertEquals("The time zone is not correct.", ZoneId.systemDefault().normalized(), ((ZonedDateTime) value).getZone());
741742
}
742743

743744
@Test

0 commit comments

Comments
 (0)