Search code examples
spring-bootutcobjectmapper

Convert String to LocalDateTime with zoned time


I received a String field in a topic with date and offset and I need to convert this String to a LocalDateTime by adding the offset. For example, if I received:

2021-07-20T19:00:00.000+02:00

I want to convert in LocalDateTime:

2021-07-20T21:00:00.000

And I have a Bean with custom object mapper for this purpose:

@Configuration
public class MyConfiguration {

     @Bean
    public MyCustomObjectMapper configure() {

        final ObjectMapper mapper = new ObjectMapper();
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX");
        final LocalDateTimeDeserializer dateTimeDeserializer = new LocalDateTimeDeserializer(formatter);
        final LocalDateTimeSerializer dateTimeSerializer = new LocalDateTimeSerializer(formatter);
        
        final JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addDeserializer(LocalDateTime.class, dateTimeDeserializer);
        javaTimeModule.addSerializer(LocalDateTime.class, dateTimeSerializer);
        mapper.registerModule(javaTimeModule);

        return new MyCustomObjectMapper (mapper);
    }

}

But it doesn't work as I expect, since the resulting LocalDateTime offset disappears and is not added:

2021-07-20T19:00:00.000

How can I achieve this goal?


Solution

  • The LocalDateTime class is a date-time representation which is unaware of time zones and it's only logical that the LocalDateTimeDeserializer ignores any time zone information in the source data.

    To account for the time zone you could use the InstantDeserializer.OFFSET_DATE_TIME deserializer (DateTimeFormatter.ISO_OFFSET_DATE_TIME is actually the format of the source date time you have) and have its result converted to LocalDateTime within a desired zone. This can be wrapped in a custom deserializer for ease of use, e.g.

    class SmartLocalDateTimeDeserializer extends StdDeserializer<LocalDateTime> {
        private final InstantDeserializer<OffsetDateTime> delegate = InstantDeserializer.OFFSET_DATE_TIME;
    
        public SmartLocalDateTimeDeserializer() {
            super(LocalDateTime.class);
        }
    
        @Override
        public LocalDateTime deserialize(JsonParser p,
                                         DeserializationContext ctxt) throws IOException, JsonProcessingException {
            final OffsetDateTime result = delegate.deserialize(p, ctxt);
            return result.atZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
        }
    }
    
    ...
    
    javaTimeModule.addDeserializer(LocalDateTime.class, new SmartLocalDateTimeDeserializer());