Given the following Unit tests:
@Test
public void zonedDateTimeCorrectlyRestoresItself() {
// construct a new instance of ZonedDateTime
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Z"));
// offset = {ZoneOffset@3820} "Z"
// zone = {ZoneOffset@3820} "Z"
String converted = now.toString();
// restore an instance of ZonedDateTime from String
ZonedDateTime restored = ZonedDateTime.parse(converted);
// offset = {ZoneOffset@3820} "Z"
// zone = {ZoneOffset@3820} "Z"
assertThat(now).isEqualTo(restored); // ALWAYS succeeds
}
@Test
public void jacksonIncorrectlyRestoresZonedDateTime() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
// construct a new instance of ZonedDateTime
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Z"));
// offset = {ZoneOffset@3820} "Z"
// zone = {ZoneOffset@3820} "Z"
String converted = objectMapper.writeValueAsString(now);
// restore an instance of ZonedDateTime from String
ZonedDateTime restored = objectMapper.readValue(converted, ZonedDateTime.class);
// offset = {ZoneOffset@3820} "Z"
// zone = {ZoneOffset@3821} "UTC"
assertThat(now).isEqualTo(restored); // NEVER succeeds
}
And this workaround:
@Test
public void usingDifferentComparisonStrategySucceeds() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
// construct a new instance of ZonedDateTime
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Z"));
// offset = {ZoneOffset@3820} "Z"
// zone = {ZoneOffset@3820} "Z"
String converted = objectMapper.writeValueAsString(now);
// restore an instance of ZonedDateTime from String
ZonedDateTime restored = objectMapper.readValue(converted, ZonedDateTime.class);
// offset = {ZoneOffset@3820} "Z"
// zone = {ZoneOffset@3821} "UTC"
// the comparison succeeds when a different comparison strategy is used
// checks whether the instants in time are equal, not the java objects
assertThat(now.isEqual(restored)).isTrue();
}
I guess I'm trying to figure out why internally Jackson just doesn't call ZonedDateTime.parse()
? Personally I think this is a bug with Jackson but I'm not confident enough to open an issue for it just yet without some feedback.
Quoting Wikipedia for ISO 8601:
If the time is in UTC, add a
Z
directly after the time without a space.Z
is the zone designator for the zero UTC offset."09:30 UTC"
is therefore represented as"09:30Z"
or"0930Z"
."14:45:15 UTC"
would be"14:45:15Z"
or"144515Z"
.UTC time is also known as Zulu time, since Zulu is the NATO phonetic alphabet word for Z.
Z
is not a Zone. UTC
is the Zone, which is then represented using Z
in a formatted string.
Don't ever use ZoneId.of("Z")
. It's wrong.