使用 Spring Boot 和 Jackson 的日期时区

Posted

技术标签:

【中文标题】使用 Spring Boot 和 Jackson 的日期时区【英文标题】:Date timezone with Spring boot and Jackson 【发布时间】:2018-03-21 01:50:28 【问题描述】:

我正在开发一个处理日期的 Spring Boot 应用程序。当我提交具有startDateTimeendDateTime 的约会对象(两者都是java.util.Date 类型)时,我发送如下格式:


    "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",

当数据保存在数据库中时,它具有正确的时区,当我尝试取回我的数据时,它们在我调试时似乎是正确的,但是一旦它们被 Jackson 序列化,我就会得到一个输出,这些值是:

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

如何配置 Jackson 以使用我的存储库中的数据附带的时区?

--------更新---------

我用OffsetDateTime 尝试了答案,但输出很奇怪:

"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
    

我想要类似的东西:

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

【问题讨论】:

使用更适合这种情况的 DateTime 类 如果OffsetDateTime 字段,您是否使用了JsonFormat 注释(如下面我的回答中所示)? 是的,我添加了这个注释,但仍然没有工作 然后将SerializationFeature.WRITE_DATES_AS_TIMESTAMPS设置为false 并且还将JavaTimeModule注册到ObjectMapper(我猜是默认不添加的) 【参考方案1】:

java.util.Datedoesn't have any timezone information。一旦将String 反序列化为Date,偏移量+0300 就会丢失:日期只保留时间戳值,它无法知道它来自哪个原始时区。

如果输出必须始终位于+03:00 偏移量中,您可以使用com.fasterxml.jackson.annotation.JsonFormat 注释直接在相应字段中设置:

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

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

这样,日期字段将始终序列化为+03:00 偏移量:


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


如果输入可以是任何其他偏移量(不仅是+03:00)并且您想保留它,那么java.util.Date 不是理想的类型。如果您使用的 Java >= 8,另一种方法是使用 Jackson Modules Java 8。

对于 Java 6 和 7,有 ThreeTen Backport 和相应的 Jackson module - 不过我还没有测试过,但代码可能相似,因为 ThreeTen Backport 包含相同的类和方法,只有包不同 - (在 Java 8 中是 java.time 而在 ThreeTen Backport 中是 org.threeten.bp

要保留日期、时间和偏移量,最好的替代方法是 OffsetDateTime 类。所以你只需要更改字段类型并为其设置相应的格式即可:

@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;

在对象映射器中,您还必须注册JavaTimeModule 并禁用ADJUST_DATES_TO_CONTEXT_TIME_ZONE feature,以便保留偏移量(默认行为是转换为杰克逊上下文的时区,这可能与输入中使用的时区不同- 通过禁用它,偏移量被保留)。

您可以使用JacksonConfigurator(如explained in this answer)并进行以下配置:

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

这个配置一般就够用了,不过你也可以把SerializationFeature.WRITE_DATES_AS_TIMESTAMPS设置成false,以防万一。


如果您仍需要使用 java.util.Date,您可以使用 API 来转换/转换它。在 Java 8 中,有新的 Date.from 方法:

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

在 ThreeTen Backport 中,有 org.threeten.bp.DateTimeUtils 类:

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

不过,要将Date 转换回OffsetDateTime,会比较棘手。 Date 对象没有时区信息,因此无法知道原始偏移量。一种替代方法是将原始偏移量保存在单独的变量中:

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

然后,您可以将Date 转换为Instant,然后将其转换为原始偏移量:

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

// ThreeTen Backport
startDateTime = DateTimeUtils.toInstant(date).atOffset(startDateOffset);

【讨论】:

spring.jackson.time-zone 配置怎么样?注释对我有用,而弹簧配置不起作用。 java.util.Date 带有时区信息,以某种方式隐含。您在记录时实际上会看到它:Sun Oct 28 02:00:00 CET 2018.【参考方案2】:

如果您将 swagger 与 spring boot 一起使用,并且您的 Date 总是被序列化。和 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS & spring.jackson.serialization.write-dates-as-timestamps=false 以下解决方案对我没有帮助。将其添加到使用 @SpringBootApplication 注释的类中:

问题:SerializationFeature.WRITE_DATES_AS_TIMESTAMPS 值未从 spring 配置文件中读取,需要将其设置为 false 以便在序列化时隐藏 long 值。

@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);
                
            );

【讨论】:

以上是关于使用 Spring Boot 和 Jackson 的日期时区的主要内容,如果未能解决你的问题,请参考以下文章

使用 Spring Boot 和 Jackson 的日期时区

Spring Boot Jackson 和数据序列化

使用 Spring Boot 和 Jackson 避免两个不同的域模型

使用 Spring Boot、Jackson 和 Hibernate 的多对多关系

Spring Boot 不使用配置的 Jackson ObjectMapper 和 @EnableWebMvc

Spring Boot没有使用Jackson Kotlin插件