7

I think I must be misunderstanding how Zones work in java's ZonedDateTime class. When I use Jackson to serialize and then deserialize now(), the deserialized value has getZone() == "UTC" instead of the "Z" in the serialized value. Can anyone explain to me why this is and what I should be doing instead?

The code below prints:

{"t":"2017-11-24T18:00:08.425Z"}
Data [t=2017-11-24T18:00:08.425Z]
Data [t=2017-11-24T18:00:08.425Z[UTC]]
Z
UTC

The java source:

<!-- language: java -->

package model;

import static org.junit.Assert.*;

import java.io.IOException;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;

import org.junit.Test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

public class ZonedDateTimeSerializationTest {

    static public class Data {
        @Override
        public String toString() {
            return "Data [t=" + t + "]";
        }

        public ZonedDateTime getT() {
            return t;
        }

        public void setT(ZonedDateTime t) {
            this.t = t;
        }

        ZonedDateTime t = ZonedDateTime.now(ZoneOffset.UTC);
    };

    @Test
    public void testDeSer() throws IOException {
        Data d = new Data();
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.findAndRegisterModules();
        String serialized = objectMapper.writer()
                .without(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                .writeValueAsString(d);
        System.out.println(serialized);

        Data d2 = objectMapper.readValue(serialized, Data.class);
        System.out.println(d);
        System.out.println(d2);
        System.out.println(d.getT().getZone());
        System.out.println(d2.getT().getZone());

        // this fails
        assertEquals(d, d2);
    }
}
Mark Wright
  • 705
  • 7
  • 20
  • I believe that `Z` comes from `ZoneOffset.UTC`, whereas `UTC` comes from `ZoneId.of("UTC")` (`ZoneOffset` is a subclass of `ZoneId`, so a `ZoneOffset` may be used as a `ZoneId`). Not that this answers your question… – Ole V.V. Nov 25 '17 at 08:55
  • And the reason you are asking is curiosity (which I share), or something beyond that? – Ole V.V. Nov 25 '17 at 08:57
  • 1
    Ole V.V. - My unit tests were failing for some simple web service calls that expected ISO8601 date times and I didn't understand why. – Mark Wright Nov 29 '17 at 08:44
  • Oh - and I was using the IDE-generated equals() in my model objects, which uses equals() and not isEqual(). – Mark Wright Nov 29 '17 at 08:53

2 Answers2

12

By default, during deserialization of a ZonedDateTime, Jackson will adjust the parsed timezone to the contextually provided one. You can modify this behavior with this setting so that the parsed ZonedDateTime will stay at Z:

objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);

More details here

Manos Nikolaidis
  • 21,608
  • 12
  • 74
  • 82
  • Thank you! “contextually provided”, does that refer to the JVM’s time zone setting and/or something similar? Could the deserialization also move the `ZonedDateTime` into a different time zone if one was “contextually provided”? Just curious. – Ole V.V. Nov 27 '17 at 08:27
  • Seconded. Thank you – Mark Wright Nov 29 '17 at 08:45
  • 2
    @OleV.V. "contextually provided" means what is returned by `DeserializationContext#getTimeZone()`. Timezone is by default UTC and not JVM default. It can be modified by `ObjectMapper#setTimeZone()`. – Manos Nikolaidis Dec 02 '17 at 14:57
2

I did this to actually preserve the timezones:

mapper.enable(SerializationFeature.WRITE_DATES_WITH_ZONE_ID)
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
anydoby
  • 408
  • 4
  • 9