60

I'm using Jackson (via Spring MVC Annotations) to deserialize a field into a java.util.Date from JSON. The POST looks like - {"enrollDate":"2011-09-28T00:00:00.000Z"}, but when the Object is created by Spring & Jackson it sets the date as "2011-09-27 20:00:00".

How can I set the proper timezone in Jackson? Or if that is not the problem, how do I send EST from the JSON message?

Javascript/jQuery:

var personDataView = { enrollDate : new Date($("#enrollDate").val()), 
                       //...other members
                      };


$.postJSON('/some/path/', personDataView, function(data){
    //... handle the response here

});

JSON Message:

{"enrollDate":"2011-09-28T00:00:00.000Z"}

Spring Controller:

@RequestMapping(value="/", method=RequestMethod.POST)
public @ResponseBody String saveProfile(@RequestBody personDataView persondataView, HttpServletRequest request)
{
        //...dataView has a java.util.Date enrollDate field
        //...other code
}
jjmontes
  • 24,679
  • 4
  • 39
  • 51
Mike G
  • 4,713
  • 2
  • 28
  • 31
  • 2
    In what timezone are you? If you're 4h west of UTC these 2 stamps are equivalent. – fvu Sep 26 '11 at 14:46
  • I'm in EST. So why is Jackson assuming the incoming time I sent is GMT? Should I change my javascript to post the date differently, or change a setting with Jackson? – Mike G Sep 26 '11 at 14:48
  • 4
    the Z at the end of the timestamp is shorthand for +00:00 aka Zulu time aka UTC / GMT. – fvu Sep 26 '11 at 14:50
  • Ah didn't know that, thanks! I've updated the question with more info regarding how the date is constructed and sent. – Mike G Sep 26 '11 at 14:59
  • I think this is unserialization, not serialization. Title should be adjusted? – jjmontes Nov 14 '11 at 13:51

9 Answers9

64

In Jackson 2+, you can also use the @JsonFormat annotation:

@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone="America/Phoenix")
private Date date;

If it doesn't work this way then try wrapping Z with single quotes, i.e. pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"

ecm
  • 2,583
  • 4
  • 21
  • 29
Olivier Lecrivain
  • 4,955
  • 2
  • 16
  • 10
26

Have you tried this in your application.properties?

spring.jackson.time-zone= # Time zone used when formatting dates. For instance `America/Los_Angeles`
20

If you really want Jackson to return a date with another time zone than UTC (and I myself have several good arguments for that, especially when some clients just don't get the timezone part) then I usually do:

ObjectMapper mapper = new ObjectMapper();
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
dateFormat.setTimeZone(TimeZone.getTimeZone("CET"));
mapper.getSerializationConfig().setDateFormat(dateFormat);
// ... etc

It has no adverse effects on those that understand the timezone-p

SiCN
  • 1,052
  • 8
  • 17
  • 20
    Seems mapper.getSerializationConfig().setDateFormat(dateFormat) is deprecated now and the new way to do it is mapper.setDateFormat(dateFormat) – Tim Nov 07 '12 at 05:25
6

Looks like older answers were fine for older Jackson versions, but since objectMapper has method setTimeZone(tz), setting time zone on a dateFormat is totally ignored.

How to properly setup timeZone to the ObjectMapper in Jackson version 2.11.0:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setTimeZone(TimeZone.getTimeZone("Europe/Warsaw"));

Full example

  @Test
  void test() throws Exception {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.findAndRegisterModules();
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    JavaTimeModule module = new JavaTimeModule();
    objectMapper.registerModule(module);
    objectMapper.setTimeZone(TimeZone.getTimeZone("Europe/Warsaw"));

    ZonedDateTime now = ZonedDateTime.now();
    String converted = objectMapper.writeValueAsString(now);

    ZonedDateTime restored = objectMapper.readValue(converted, ZonedDateTime.class);
    System.out.println("serialized: " + now);
    System.out.println("converted: " + converted);
    System.out.println("restored: " + restored);

    Assertions.assertThat(now).isEqualTo(restored);
  }
`
LukaszH
  • 151
  • 1
  • 2
  • 7
  • 2
    I use the same approach, but set JVM time zone mapper.setTimeZone(TimeZone.getDefault()), to avoid hardcoding ay particular time zone. – Maksim Sirotkin Aug 29 '21 at 12:38
5

I am using Jackson 1.9.7 and I found that doing the following does not solve my serialization/deserialization timezone issue:

DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSSZ");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
objectMapper.setDateFormat(dateFormat);

Instead of "2014-02-13T20:09:09.859Z" I get "2014-02-13T08:09:09.859+0000" in the JSON message which is obviously incorrect. I don't have time to step through the Jackson library source code to figure out why this occurs, however I found that if I just specify the Jackson provided ISO8601DateFormat class to the ObjectMapper.setDateFormat method the date is correct.

Except this doesn't put the milliseconds in the format which is what I want so I sub-classed the ISO8601DateFormat class and overrode the format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) method.

/**
 * Provides a ISO8601 date format implementation that includes milliseconds
 *
 */
public class ISO8601DateFormatWithMillis extends ISO8601DateFormat {

  /**
   * For serialization
   */
  private static final long serialVersionUID = 2672976499021731672L;


  @Override
  public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition)
  {
      String value = ISO8601Utils.format(date, true);
      toAppendTo.append(value);
      return toAppendTo;
  }
}
whitestryder
  • 451
  • 5
  • 10
  • The output exactly matches the SimpleDateFormat that you gave it. Per [SimpleDateFormat Javadoc](http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html): hh outputs 12 hour time, kk outputs 24 time, and Z outputs the timezone offset. I think you wanted an explicit date format of `yyyy-MM-dd'T'kk:mm:ss.SSS'Z'`. – kaliatech Feb 26 '14 at 20:17
  • The format of the date was a minor issue, the major issue for me was that the actual date values being spit out by the ObjectMapper were incorrect. Look carefully at the example I gave, the date output was actually 12 hours off from what was expected, that is, instead of "2014-02-13T20:09:09.859Z" I got "2014-02-13T08:09:09.859+0000" – whitestryder Mar 07 '14 at 08:16
  • 08:09:09 in _12_ hour time (with the am/pm indicator missing) is equivalent to 20:09:09 in _24_ hour time. – kaliatech Mar 07 '14 at 13:19
  • You're right, I got confused by the difference between ISO8601 Datetime format specification and SimpleDateFormat, they are different. In ISO8601 'hh' means 24 hour clock, but in SimpleDateFormat it means 12 hour clock (http://www.w3.org/TR/NOTE-datetime) Arggh! So my solution still works but was unnecessary in my scenario. Thanks. – whitestryder Mar 07 '14 at 17:35
2

Just came into this issue and finally realised that LocalDateTime doesn't have any timezone information. If you received a date string with timezone information, you need to use this as the type:

ZonedDateTime

Check this link

Lichader
  • 340
  • 3
  • 13
  • I kept on getting this error when trying to specify a timezone for a LocalDateTime even though the pattern included a timezone (pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ) . I changed it back to a Date and everything started to work. JSON parse error: Invalid format: "2019-04-01T00:00:00+0300" is malformed at "+0300"; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Invalid format: "2019-04-01T00:00:00+0300" is malformed at "+0300" – AmanicA Jun 11 '19 at 12:54
  • @AmanicA How can you specify a timezone for LocalDateTime object? The local one should always default to your local time zone. I don't think you can change that. You'd better use ZonedDateTime like I suggested in the post. – Lichader Jun 14 '19 at 06:12
1

I had same problem with Calendar deserialization, solved extending CalendarDeserializer.
It forces UTC Timezone
I paste the code if someone need it:

@JacksonStdImpl
public class UtcCalendarDeserializer extends CalendarDeserializer {

    TimeZone TZ_UTC = TimeZone.getTimeZone("UTC");

    @Override
    public Calendar deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonToken t = jp.getCurrentToken();
        if (t == JsonToken.VALUE_NUMBER_INT) {
            Calendar cal = Calendar.getInstance(TZ_UTC);
            cal.clear();
            cal.setTimeInMillis(jp.getLongValue());

            return cal;
        }

        return super.deserialize(jp, ctxt);
    }
}

in JSON model class just annotate the field with:

@JsonDeserialize(using = UtcCalendarDeserializer.class)
private Calendar myCalendar;
fl4l
  • 1,580
  • 4
  • 21
  • 27
1

For anyone struggling with this problem in the now (Feb 2020), the following Medium post was crucial to overcoming it for us.

https://medium.com/@ttulka/spring-http-message-converters-customizing-770814eb2b55

In our case, the app uses @EnableWebMvc and would break if removed so, the section on 'The Life without Spring Boot' was critical. Here's what ended up solving this for us. It allows us to still consume and produce JSON and XML as well as format our datetime during serialization to suit the app's needs.

@Configuration
@ComponentScan("com.company.branch")
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(0, new MappingJackson2XmlHttpMessageConverter(
                new Jackson2ObjectMapperBuilder()
                        .defaultUseWrapper(false)
                        .createXmlMapper(true)
                        .simpleDateFormat("yyyy-mm-dd'T'HH:mm:ss'Z'")
                        .build()
        ));
    converters.add(1, new MappingJackson2HttpMessageConverter(
                new Jackson2ObjectMapperBuilder()
                        .build()
        ));
    }
}
robrides
  • 51
  • 4
1

Your date object is probably ok, since you sent your date encoded in ISO format with GMT timezone and you are in EST when you print your date.

Note that Date objects perform timezone translation at the moment they are printed. You can check if your date object is correct with:

Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
cal.setTime(date);
System.out.println (cal);
jjmontes
  • 24,679
  • 4
  • 39
  • 51