Skip to content

Commit 8a6ea96

Browse files
committed
Only normalize OffsetDateTimes when they are normalizable
Possible fix for FasterXML#166. This commit adds a check before the OffsetDateTime adjustment of ADJUST_DATES_TO_CONTEXT_TIME_ZONE that ensures that the OffsetDateTime is normalizeable given a specific time zone offset. When it is not, normalization is skipped and the un-adjusted OffsetDateTime is returned.
1 parent bb734fc commit 8a6ea96

File tree

2 files changed

+101
-6
lines changed

2 files changed

+101
-6
lines changed

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

+28-6
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,7 @@
2929

3030
import java.io.IOException;
3131
import java.math.BigDecimal;
32-
import java.time.DateTimeException;
33-
import java.time.Instant;
34-
import java.time.OffsetDateTime;
35-
import java.time.ZoneId;
36-
import java.time.ZonedDateTime;
32+
import java.time.*;
3733
import java.time.format.DateTimeFormatter;
3834
import java.time.temporal.Temporal;
3935
import java.time.temporal.TemporalAccessor;
@@ -69,7 +65,15 @@ public class InstantDeserializer<T extends Temporal>
6965
OffsetDateTime::from,
7066
a -> OffsetDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId),
7167
a -> OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId),
72-
(d, z) -> d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime())),
68+
(d, z) -> {
69+
ZoneOffset o = z.getRules().getOffset(d.toLocalDateTime());
70+
// Only adjust the OffsetDateTime when it is normalizable
71+
if (isAdjustable(d, o)) {
72+
return d.withOffsetSameInstant(o);
73+
} else {
74+
return d;
75+
}
76+
},
7377
true // yes, replace zero offset with Z
7478
);
7579

@@ -320,6 +324,24 @@ private String replaceZeroOffsetAsZIfNecessary(String text)
320324
return text;
321325
}
322326

327+
/**
328+
* Checks whether an {@link OffsetDateTime} is normalizable to a certain
329+
* {@link ZoneOffset}. See [jackson-modules-java8#166].
330+
*
331+
* @param d The {@link OffsetDateTime} to normalize
332+
* @param o The desired {@link ZoneOffset} of the normalization
333+
* @return true, when the {@link OffsetDateTime} can be normalized to the
334+
* given {@link ZoneOffset}, false else
335+
*/
336+
private static boolean isAdjustable(OffsetDateTime d, ZoneOffset o)
337+
{
338+
OffsetDateTime lowerBoundEx = OffsetDateTime.MIN
339+
.plusHours(18).minusSeconds(o.getTotalSeconds()).minusNanos(1);
340+
OffsetDateTime upperBoundEx = OffsetDateTime.MAX
341+
.minusHours(18).minusSeconds(o.getTotalSeconds()).plusNanos(1);
342+
return d.isAfter(lowerBoundEx) && d.isBefore(upperBoundEx);
343+
}
344+
323345
public static class FromIntegerArguments // since 2.8.3
324346
{
325347
public final long value;

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

+73
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,79 @@ public void testRoundTripOfOffsetDateTimeAndJavaUtilDate() throws Exception
522522
assertEquals(givenInstant.atOffset(ZoneOffset.UTC), actual);
523523
}
524524

525+
/*
526+
* Tests for the deserialization of OffsetDateTimes that cannot be
527+
* normalized with ADJUST_DATES_TO_CONTEXT_TIME_ZONE enabled. The expected
528+
* behaviour is that normalization is skipped for those OffsetDateTimes
529+
* that cannot be normalized. See [jackson-modules-java8#166].
530+
*/
531+
532+
@Test
533+
public void testDeserializationOfOffsetDateTimeMin() throws Exception
534+
{
535+
OffsetDateTime date = OffsetDateTime.MIN;
536+
OffsetDateTime value = MAPPER.readerFor(OffsetDateTime.class)
537+
.with(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
538+
.readValue('"' + FORMATTER.format(date) + '"');
539+
assertIsEqual(date, value);
540+
assertNotEquals("The time zone has been normalized.", ZoneOffset.UTC, value.getOffset());
541+
}
542+
543+
@Test
544+
public void testDeserializationOfUnadjustableOffsetDateTimeNearMin() throws Exception
545+
{
546+
OffsetDateTime date = OffsetDateTime.MIN.plusHours(18).minusNanos(1);
547+
OffsetDateTime value = MAPPER.readerFor(OffsetDateTime.class)
548+
.with(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
549+
.readValue('"' + FORMATTER.format(date) + '"');
550+
assertIsEqual(date, value);
551+
assertNotEquals("The time zone has been normalized.", ZoneOffset.UTC, value.getOffset());
552+
}
553+
554+
@Test
555+
public void testDeserializationOfAdjustableOffsetDateTimeNearMin() throws Exception
556+
{
557+
OffsetDateTime date = OffsetDateTime.MIN.plusHours(18);
558+
OffsetDateTime value = MAPPER.readerFor(OffsetDateTime.class)
559+
.with(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
560+
.readValue('"' + FORMATTER.format(date) + '"');
561+
assertIsEqual(date, value);
562+
assertEquals("The time zone is not correct.", ZoneOffset.UTC, value.getOffset());
563+
}
564+
565+
@Test
566+
public void testDeserializationOfOffsetDateTimeMax() throws Exception
567+
{
568+
OffsetDateTime date = OffsetDateTime.MAX;
569+
OffsetDateTime value = MAPPER.readerFor(OffsetDateTime.class)
570+
.with(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
571+
.readValue('"' + FORMATTER.format(date) + '"');
572+
assertIsEqual(date, value);
573+
assertNotEquals("The time zone has been normalized.", ZoneOffset.UTC, value.getOffset());
574+
}
575+
576+
@Test
577+
public void testDeserializationOfUnadjustableOffsetDateTimeNearMax() throws Exception
578+
{
579+
OffsetDateTime date = OffsetDateTime.MAX.minusHours(18).plusNanos(1);
580+
OffsetDateTime value = MAPPER.readerFor(OffsetDateTime.class)
581+
.with(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
582+
.readValue('"' + FORMATTER.format(date) + '"');
583+
assertIsEqual(date, value);
584+
assertNotEquals("The time zone has been normalized.", ZoneOffset.UTC, value.getOffset());
585+
}
586+
587+
@Test
588+
public void testDeserializationOfAdjustableOffsetDateTimeNearMax() throws Exception
589+
{
590+
OffsetDateTime date = OffsetDateTime.MAX.minusHours(18);
591+
OffsetDateTime value = MAPPER.readerFor(OffsetDateTime.class)
592+
.with(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
593+
.readValue('"' + FORMATTER.format(date) + '"');
594+
assertIsEqual(date, value);
595+
assertEquals("The time zone is not correct.", ZoneOffset.UTC, value.getOffset());
596+
}
597+
525598
/*
526599
/**********************************************************
527600
/* Tests for empty string handling

0 commit comments

Comments
 (0)