公司有个 ERP 对接的场景,我们需要将数据上报的接入方系统。在上报一段时间后发现订单数据的下单时间不正确,与正确的下单时间少了8小时…

说到这基本上就明白咋回事了,我当时特别奇怪。为了测试时间我还特意 DEBUG 了一下,结果显示 Domain 查询出来的日期是没问题的,但是经过 Jackson 序列化处理之后时间就少了八小时。当时是特别的诧异,现在我来复现下场景:

场景复现

创建一个普通的 User 类:

  1. @Setter
  2. @Getter
  3. @ToString
  4. class User {
  5. private String username;
  6. @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  7. private Date date;
  8. }

注意看属性 date 字段,类型是 java.util.Date。并使用了 Jsckson 的 com.fasterxml.jackson.annotation.JsonFormat 注解指定日期格式化格式。

先看下当前操作系统的时间:

  1. $ date +%Y-%m-%d\ %H:%M:%S
  2. 2021-09-25 10:48:30

如果正常的话那我系统输出的日期应该也是 10:48。但是实际上呢?下面来使用 ObjectMapper 做系列化:

  1. public static void main(String[] args) throws JsonProcessingException {
  2. ObjectMapper objectMapper = new ObjectMapper();
  3. // 格式化输出
  4. objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
  5. User user = new User();
  6. user.setUsername("zhangsan");
  7. user.setDate(new Date());
  8. System.out.println(objectMapper.writeValueAsString(user));
  9. }

结果输出如下:

  1. {
  2. "username" : "zhangsan",
  3. "date" : "2021-09-25 02:48:38"
  4. }

对比下时间你会发现整整差了8小时,但是如果将 java.util.Date 换成 java.time.LocalDateTime 就不会有这个问题,说到底还是时区的问题。

解决办法

为了解决该问题我看了下 com.fasterxml.jackson.annotation.JsonFormat 注解中对时区的说明,如下截图:
Screen Shot 2021-09-25 at 10.53.54.png
也就是说,Jackson 的 com.fasterxml.jackson.annotation.JsonFormat 注解在做日期格式化时默认选择的是 UTC 时区,而不是 JVM 读取的操作系统的时区。

知道了这点我们手动设置下时区不就好了吗?所以在创建 ObjectMapper 实例的时候我们做下时区的配置,将时区设置为 JVM 读取的操作系统的时区(推荐):

  1. public static void main(String[] args) throws JsonProcessingException {
  2. ObjectMapper objectMapper = new ObjectMapper();
  3. // 格式化输出
  4. objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
  5. // 设置时区
  6. objectMapper.setTimeZone(TimeZone.getDefault());
  7. User user = new User();
  8. user.setUsername("zhangsan");
  9. user.setDate(new Date());
  10. System.out.println(objectMapper.writeValueAsString(user));
  11. }

再次序列化就会发现时间正常了:

  1. {
  2. "username" : "zhangsan",
  3. "date" : "2021-09-25 10:59:16"
  4. }
疑问
在设置时区时为什么选择 TimeZone.getDefault() ,直接指定具体时区不好吗?指定具体时区没什么问题,比如这个就可以选择中国时区:TimeZone.getTimeZone("Asia/Shanghai")。但是不推荐,因为如果公司的业务在世界范围,在部署时都是容器化部署。你不可能为每个容器都定制一下时区吧?所以直接设置为 JVM 系统时区更适应业务。