11

I want to configure jackson to output any date/time values with the following format:

spring.jackson.date-format=yyyy-MM-dd'T'HH:mm:ss

I'm fetching many database rows and return them just as a json map.

@RestController
public class MyService {
    @GetMapping
    public List<Map<String, Object>> get(Param params) {
             return jdbcTemplate.queryForList(sql, params);
    }
}

Problem: the databases and jvm default timezone is Europe/Berlin, thus UTC+2. Therefor jackson automatically converts any database-received java.sql.Timestamp to UTC first (subtracts 2 hours), and then outputs them via json.

In the mysql database itself, it's a datetime type.

But I just want jackson to output the timestamps "as is", without prior conversion! Is that possible to skip timezone correction?

I just want to ignore the timezone without conversation. Just cut it.

membersound
  • 81,582
  • 193
  • 585
  • 1,120
  • What is your database column datatype? Could you avoid the `Timestamp` class? It is long outdated. It sounds like you should want the `LocalDateTime` datatype from `java.time`, the modern Java date and time API. It doesn’t have a time zone, so should prevent any time zone issues. `Instant` is another option depending on circumstances. – Ole V.V. Apr 26 '18 at 14:58
  • What's the `mysql` equivalent that I'd have to use in order to automatically obtain a `LocalDateTime` instead of `java.sql.Timestamp`? Indeed currently it's a `mysql datetime` datatype. – membersound Apr 26 '18 at 15:11
  • This question might have the answer you are looking for [Jackson Serialization Ignore Timezone](https://stackoverflow.com/questions/34519938/jackson-serialization-ignore-timezone) – David D Apr 26 '18 at 18:09
  • This question is what you need I think.https://stackoverflow.com/questions/46151633/how-to-make-default-time-zone-apply-in-spring-boot-jackson-date-serialization – WesternGun Nov 07 '18 at 13:31

2 Answers2

8

Approach #1: Setting a default time zone

You could set a time zone in the date format used by ObjectMapper. It will be used for Date and subclasses:

DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));

ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(dateFormat);

In Spring applications, to configure ObjectMapper, you can do as follows:

@Bean
public ObjectMapper objectMapper() {

    DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));

    ObjectMapper mapper = new ObjectMapper();
    mapper.setDateFormat(dateFormat);
    return mapper;
}

In Spring Boot you can use the property spring.jackson.time-zone to define the timezone:

spring.jackson.time-zone: Europe/Berlin

For more details on the common application properties, refer to the documentation.

Approach #2: Using the Java 8 Date and Time API

Instead of using Timestamp, you could consider LocaDateTime from the JSR-310. It was introduced in Java 8. The "local" date and time classes (LocalDateTime, LocalDate and LocalTime) are not tied to any one locality or time zone. From the LocalDateTime documentation:

This class does not store or represent a time-zone. Instead, it is a description of the date, as used for birthdays, combined with the local time as seen on a wall clock. It cannot represent an instant on the time-line without additional information such as an offset or time-zone.

This answer will give you more details on the new date and time classes.

Jackson has a module that supports JSR-310 types. Add it to your dependencies:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.9</version>
</dependency>

Then register the JavaTimeModule module in your ObjectMapper instance:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

Most JSR-310 types will be serialized using a standard ISO-8601 string representation. If you need a custom format, you can use your own serializer and deserializer implementation. See the documentation for details.

cassiomolin
  • 124,154
  • 35
  • 280
  • 359
  • Ok that works, but can't I just ignore the timezone? I'm connecting to multiple databases where I don't know the timezone beforehand. And as far as I understood, I'd have to set the databases timezone into the `DateFormat` in your suggestion? – membersound Apr 26 '18 at 15:22
  • @membersound If you have different time zones and you deliberately ignore them, won't you have inconsistent results? I have added an alternative approach with `LocalDateTime` to my answer. – cassiomolin Apr 27 '18 at 09:15
  • 1
    Great answer! If time zone information is relevant, wouldn’t it be better to use a `ZonedDateTime` instead of a `LocalDateTime` or at least a `OffsetDateTome` to account for the time differences between databases solely based on their offsets? – Edwin Dalorzo Apr 27 '18 at 10:07
  • @CassioMazzochiMolin in my case I have to work with a legacy database. I just want to output the datetime values "as is" from the database via the json webservice. Any timezone correction is irrelevant, I just want to show the same value via the webservice that exists in the database (and as the mysql `datetime` type is independent of `timezone`, I also want to prevent timezone corrections in java. – membersound Apr 27 '18 at 13:24
  • @membersound So attempt to use `LocaDateTime` for that purpose. – cassiomolin Apr 27 '18 at 13:29
  • I would **if** I'd create bean fields. But as you can see in my initial question, I'm just returning all columns returned from `jdbctemplate`. And I cannot tell the template to return `LocalDateTime` instead of `java.sql.Timestamp`, could I? – membersound Apr 27 '18 at 17:30
  • 1
    @membersound [You could try to use a converter](https://stackoverflow.com/a/37874877/1426227). – cassiomolin Apr 27 '18 at 17:50
5

Finally it turned out the simples way is to just set the jacksons ObjectMapper (which uses UTC by defaut) timezone to the jvm defaults:

@Bean
public Jackson2ObjectMapperBuilderCustomizer init() {
    return new Jackson2ObjectMapperBuilderCustomizer() {
        @Override
        public void customize(Jackson2ObjectMapperBuilder builder) {
            builder.timeZone(TimeZone.getDefault());
        }
    };
}

I'd appreciate if anybody knows how I can achieve the same by just using the spring.jackson.time-zone application.property.

membersound
  • 81,582
  • 193
  • 585
  • 1,120