12

I'm developing a spring boot application that handles dates. When I submit an Appointment object that has a startDateTime and an endDateTime (Both are of type java.util.Date) I send a format like this:

{
    "lastName": "Jhon",
    "firstName": "Doe",
    "email": "jhon.doe@gmail.com",
    "description": "MyDescription",
    "startDateTime": "2017-10-09T22:43:07.109+0300",
    "endDateTime": "2017-10-09T21:40:07.109+0300",
}

When data is persisted in database it's with the correct timezone, when I try to retrieve my data back, they seem correct when I'm debugging however, once they are serialized by Jackson I have an output with these as value:

"startDateTime": "2017-10-09T19:43:07.109+0000",
"endDateTime": "2017-10-09T18:40:07.109+0000",

How can I configure Jackson to make usage of timezone that comes with the data from my repository?

------Update---------

I tried the answer with OffsetDateTime but the output is quite weird:

"startDateTime": {
        "offset": {
            "totalSeconds": 7200,
            "id": "+02:00",
            "rules": {
                "fixedOffset": true,
                "transitionRules": [],
                "transitions": []
            }
        },
        "month": "OCTOBER",
        "year": 2017,
        "hour": 21,
        "minute": 49,
        "nano": 654000000,
        "second": 15,
        "dayOfMonth": 9,
        "dayOfWeek": "MONDAY",
        "dayOfYear": 282,
        "monthValue": 10
    }

I would like to have something like:

2017-10-09T22:43:07.109+0300

Habchi
  • 1,921
  • 2
  • 22
  • 50
  • use DateTime class which is more suitable for this case – CodeIsLife Oct 09 '17 at 19:31
  • Have you used the `JsonFormat` annotation if the `OffsetDateTime` fields (as it's in my answer below)? –  Oct 09 '17 at 19:55
  • Yes I added this annotation, but still didn't work – Habchi Oct 09 '17 at 19:56
  • Then set `SerializationFeature.WRITE_DATES_AS_TIMESTAMPS` to false –  Oct 09 '17 at 19:56
  • And also register the `JavaTimeModule` to the `ObjectMapper` (it's not added by default, I guess) –  Oct 09 '17 at 19:58
  • I can't find JavaTimeModule class :/ – Habchi Oct 09 '17 at 19:59
  • It's in the java 8 module that I also mention in my answer: https://github.com/FasterXML/jackson-modules-java8 –  Oct 09 '17 at 20:00
  • I just added the dependencies, and it works like a charm. I was wondering if there's no configuration needed for the persistence part with hibernate. Anyway, thank you sir – Habchi Oct 09 '17 at 20:05
  • Yes, you need additional configs for hibernate: https://stackoverflow.com/questions/23718383/jpa-support-for-java-8-new-date-and-time-api - https://stackoverflow.com/a/46039236/7605325 –  Oct 09 '17 at 20:55
  • You're welcome, glad to help! –  Oct 09 '17 at 21:04

2 Answers2

13

A java.util.Date doesn't have any timezone information. Once you deserialize a String to a Date, the offset +0300 is lost: the date keeps just the timestamp value, and it can't know what's the original timezone it came from.

If the output must always be in +03:00 offset, you can set it directly in the respective fields, using the com.fasterxml.jackson.annotation.JsonFormat annotation:

@JsonFormat(timezone = "GMT+03:00")
private Date startDateTime;

@JsonFormat(timezone = "GMT+03:00")
private Date endDateTime;

With this, the date fields will always be serialized to +03:00 offset:

{
  "startDateTime":"2017-10-09T22:43:07.109+0300",
  "endDateTime":"2017-10-09T21:40:07.109+0300"
}

If the inputs can be in any other offset (not only +03:00) and you want to preserve it, the java.util.Date isn't the ideal type. One alternative is to use Jackson Modules Java 8, if you're using Java >= 8.

For Java 6 and 7, there's the ThreeTen Backport and the corresponding Jackson module - I haven't tested, though, but the code might be similar, as the ThreeTen Backport contains the same classes and methods, only the package is different - (in Java 8 is java.time and in ThreeTen Backport is org.threeten.bp).

To preserve the date, time and offset, the best alternative is the OffsetDateTime class. So you just need to change the fields type and set the corresponding format to it:

@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXX")
private OffsetDateTime startDateTime;

@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXX")
private OffsetDateTime endDateTime;

In the object mapper, you must also register the JavaTimeModule and disable the ADJUST_DATES_TO_CONTEXT_TIME_ZONE feature, so the offsets are preserved (the default behaviour is to convert to Jackson context's timezone, which might not be the same used in the inputs - by disabling this, the offset is preserved).

You can use a JacksonConfigurator (as explained in this answer) and do these configurations:

ObjectMapper om = new ObjectMapper();
om.registerModule(new JavaTimeModule());
om.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false);

This configuration is usually enough, but you can also set SerializationFeature.WRITE_DATES_AS_TIMESTAMPS to false as well, just in case.


If you still need to work with java.util.Date, you can use the API to convert from/to it. In Java 8, there's the new Date.from method:

// convert to java.util.Date
public Date getStartAsJavaUtilDate() {
    return Date.from(startDateTime.toInstant());
}

And in ThreeTen Backport, there's the org.threeten.bp.DateTimeUtils class:

// convert to java.util.Date
DateTimeUtils.toDate(startDateTime.toInstant());

To convert a Date back to OffsetDateTime, though, it's more tricky. The Date object has no timezone information, so it can't know the original offset. One alternative is to keep the original offset in a separate variable:

// keep the original offset
ZoneOffset startDateOffset = startDateTime.getOffset();

Then, you can convert the Date to Instant, and then convert it to the original offset:

// convert java.util.Date to original offset (Java 8)
startDateTime = date.toInstant().atOffset(startDateOffset);

// ThreeTen Backport
startDateTime = DateTimeUtils.toInstant(date).atOffset(startDateOffset);
  • 1
    how about spring.jackson.time-zone config? The annotation works for me while the spring config dose not. – Sheldon Wei Feb 26 '18 at 09:37
  • `java.util.Date` comes with timezone info, somehow implicit. You actually sees it when logging it: `Sun Oct 28 02:00:00 CET 2018`. – WesternGun Nov 07 '18 at 12:15
-1

If you are using swagger with spring boot and your Date is always getting serialised as long. 
And SerializationFeature.WRITE_DATES_AS_TIMESTAMPS & spring.jackson.serialization.write-dates-as-timestamps=false are not helping below solution worked for me. Add it to your class annotated with @SpringBootApplication:

Issue: SerializationFeature.WRITE_DATES_AS_TIMESTAMPS value is not read from spring configuration file which need to be set false in order 
to covert long value while serialisation.

@Autowired
private RequestMappingHandlerAdapter handlerAdapter;

@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
    handlerAdapter
            .getMessageConverters()
            .stream()
            .forEach(c -> {
                if (c instanceof MappingJackson2HttpMessageConverter) {
                    MappingJackson2HttpMessageConverter jsonMessageConverter = (MappingJackson2HttpMessageConverter) c;
                    ObjectMapper objectMapper = jsonMessageConverter.getObjectMapper();
                    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
                }
            });
}
abhijeet badale
  • 149
  • 1
  • 3