Skip to content

#166: Only normalize OffsetDateTimes when they are normalizable #172

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

Closed
wants to merge 1 commit into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,7 @@

import java.io.IOException;
import java.math.BigDecimal;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
Expand Down Expand Up @@ -69,7 +65,15 @@ public class InstantDeserializer<T extends Temporal>
OffsetDateTime::from,
a -> OffsetDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId),
a -> OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId),
(d, z) -> d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime())),
(d, z) -> {
ZoneOffset o = z.getRules().getOffset(d.toLocalDateTime());
// Only adjust the OffsetDateTime when it is normalizable
if (isAdjustable(d, o)) {
return d.withOffsetSameInstant(o);
} else {
return d;
}
},
true // yes, replace zero offset with Z
);

Expand Down Expand Up @@ -320,6 +324,24 @@ private String replaceZeroOffsetAsZIfNecessary(String text)
return text;
}

/**
* Checks whether an {@link OffsetDateTime} is normalizable to a certain
* {@link ZoneOffset}. See [jackson-modules-java8#166].
*
* @param d The {@link OffsetDateTime} to normalize
* @param o The desired {@link ZoneOffset} of the normalization
* @return true, when the {@link OffsetDateTime} can be normalized to the
* given {@link ZoneOffset}, false else
*/
private static boolean isAdjustable(OffsetDateTime d, ZoneOffset o)
{
OffsetDateTime lowerBoundEx = OffsetDateTime.MIN
.plusHours(18).minusSeconds(o.getTotalSeconds()).minusNanos(1);
OffsetDateTime upperBoundEx = OffsetDateTime.MAX
.minusHours(18).minusSeconds(o.getTotalSeconds()).plusNanos(1);
return d.isAfter(lowerBoundEx) && d.isBefore(upperBoundEx);
}

public static class FromIntegerArguments // since 2.8.3
{
public final long value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,79 @@ public void testRoundTripOfOffsetDateTimeAndJavaUtilDate() throws Exception
assertEquals(givenInstant.atOffset(ZoneOffset.UTC), actual);
}

/*
* Tests for the deserialization of OffsetDateTimes that cannot be
* normalized with ADJUST_DATES_TO_CONTEXT_TIME_ZONE enabled. The expected
* behaviour is that normalization is skipped for those OffsetDateTimes
* that cannot be normalized. See [jackson-modules-java8#166].
*/

@Test
public void testDeserializationOfOffsetDateTimeMin() throws Exception
{
OffsetDateTime date = OffsetDateTime.MIN;
OffsetDateTime value = MAPPER.readerFor(OffsetDateTime.class)
.with(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.readValue('"' + FORMATTER.format(date) + '"');
assertIsEqual(date, value);
assertNotEquals("The time zone has been normalized.", ZoneOffset.UTC, value.getOffset());
}

@Test
public void testDeserializationOfUnadjustableOffsetDateTimeNearMin() throws Exception
{
OffsetDateTime date = OffsetDateTime.MIN.plusHours(18).minusNanos(1);
OffsetDateTime value = MAPPER.readerFor(OffsetDateTime.class)
.with(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.readValue('"' + FORMATTER.format(date) + '"');
assertIsEqual(date, value);
assertNotEquals("The time zone has been normalized.", ZoneOffset.UTC, value.getOffset());
}

@Test
public void testDeserializationOfAdjustableOffsetDateTimeNearMin() throws Exception
{
OffsetDateTime date = OffsetDateTime.MIN.plusHours(18);
OffsetDateTime value = MAPPER.readerFor(OffsetDateTime.class)
.with(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.readValue('"' + FORMATTER.format(date) + '"');
assertIsEqual(date, value);
assertEquals("The time zone is not correct.", ZoneOffset.UTC, value.getOffset());
}

@Test
public void testDeserializationOfOffsetDateTimeMax() throws Exception
{
OffsetDateTime date = OffsetDateTime.MAX;
OffsetDateTime value = MAPPER.readerFor(OffsetDateTime.class)
.with(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.readValue('"' + FORMATTER.format(date) + '"');
assertIsEqual(date, value);
assertNotEquals("The time zone has been normalized.", ZoneOffset.UTC, value.getOffset());
}

@Test
public void testDeserializationOfUnadjustableOffsetDateTimeNearMax() throws Exception
{
OffsetDateTime date = OffsetDateTime.MAX.minusHours(18).plusNanos(1);
OffsetDateTime value = MAPPER.readerFor(OffsetDateTime.class)
.with(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.readValue('"' + FORMATTER.format(date) + '"');
assertIsEqual(date, value);
assertNotEquals("The time zone has been normalized.", ZoneOffset.UTC, value.getOffset());
}

@Test
public void testDeserializationOfAdjustableOffsetDateTimeNearMax() throws Exception
{
OffsetDateTime date = OffsetDateTime.MAX.minusHours(18);
OffsetDateTime value = MAPPER.readerFor(OffsetDateTime.class)
.with(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.readValue('"' + FORMATTER.format(date) + '"');
assertIsEqual(date, value);
assertEquals("The time zone is not correct.", ZoneOffset.UTC, value.getOffset());
}

/*
/**********************************************************
/* Tests for empty string handling
Expand Down