首页 > 其他分享 >夏令时踩坑记录二

夏令时踩坑记录二

时间:2023-05-25 11:48:01浏览次数:41  
标签:服务 记录 -- 夏令时 TimeZone 时区 public GMT

一、现象描述

第三方数据上传,上传上来发现有一个人的出生日变成了 1991-07-15 23:00:00。

这个时间明显不对,理论上这个的生日是1991年7月16号。

曾经踩过夏令时的坑, 一看这个就知道应该是夏令时时区转换导致时间相差一个小时。

二、问题分析与解决

2.1 数据流转说明

欲分析是哪里出现了时区转换,那么我们得把数据的流转搞清楚。

这个数据流转如下:第三方系统(java应用) --> api接口服务(java应用) --> 数据存储服务(java应用) --> 数据库

2.2 各个环节分析

2.2.1 第三方系统 --> api接口服务

  • 这个环节中,都是日期字符串传输,不会发生时区转换

2.2.2 api接口服务 --> 数据存储服务

  • api接口服务内部用Date类型来接收的日期字符串(这里要考虑时区问题)
  • api接口再将Data转成了字符串,传输到了数据存储服务(该里也要考虑时区问题)

2.2.3 数据存储服务 --> 数据库

  • 在数据存储服务里面,看到参数是用Java的Date来接收的(这里也要考虑时区问题)
  • 数据库用的时区是GMT+8

2.3 问题分析

2.3.1 发现疑点

从上面的数据流转各个环节分析,逐个系统排查。api接口服务排查,情况如下:

  • 字符串-->Date-->字符串,这两步用了同一个时区,不会发生转换
  • 通过该系统打印的日志,发现接口服务往数据存储服务发的日期字符串还是 1991-07-16
  • 通过分析发现api接口服务不会发生时区转换

数据存储服务排查,情况如下:

  • 发现用的是FastJson进行了的JSON反序列化
  • 通过打印的日志,发现输出了一个时间的毫秒值,值为679590000000L
  • 这个值就有意思了,通过如下代码验证,发现在系统时间为GMT+8时,该毫秒值对应日期为1991-07-15 23:00:00,参考下面代码
    
public class TestDate {
    public static void main(String[] args) {
        TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                .format(new Date(679590000000L)));
        // 输出结果为 1991-07-15 23:00:00
    }
}
  • 通过这个分析,那么就发现了这个日期在FastJson转换时,用了Asia/Shanghai;上面代码中把时区设置为上海时,输出的结果就是1991-07-16

2.3.2 分析原因

通过上一章节分析,知道了是数据存储服务中FastJson用了上海时区导致了时区转换。那么需要分析为何数据存储服务里面会有2个不同的时区。SpringBoot项目,直接从服务启动类看,服务是指定了时区的;代码如下

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        // fastjson开启安全模式
        ParserConfig.getGlobalInstance().setSafeMode(true);
        SpringApplication.run(Application.class, args);
    }

    @PostConstruct
    public void init () {
        // 设置系统时区
        TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("GMT+8")));
    }
}

简单分析:

  1. 第一时间看该代码,好像没啥问题,init方法里面指定了时区统一用GMT+8
  2. 从git提交记录发现了端倪,发现这个fastjson开启安全模式的代码是近期加的

猜想与验证:突然灵光一闪:这行代码运行是在init方法前的,如果此时fastjson获取时区的话,那么就会用系统时区;就有可能用了 Asia/Shanghai。因此,直接把设置系统时区的代码放到main方法第一行去,验证问题是否解决?代码如下:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
         // 设置系统时区
        TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("GMT+8")));
        // fastjson开启安全模式
        ParserConfig.getGlobalInstance().setSafeMode(true);
        SpringApplication.run(Application.class, args);
    }

}

发现问题解决了!!!从源码找答案:分析fastJson的源码,发现JSON类被加载就会获取时区


public abstract class JSON implements JSONStreamAware, JSONAware {
    public static TimeZone         defaultTimeZone      = TimeZone.getDefault();
    public static Locale           defaultLocale        = Locale.getDefault();
    // ...其他源码就不贴了
} 

   

因此是fastjson开启安全模式的代码导致JSON类被提取加载,从而读取了系统时区并设置到静态变量里去了

三、解决方案

3.1 临时方案

在不更新服务的情况下,线上启动数据存储java服务时通过启动命令指定为东八区

-Duser.timezone="GMT+8" 

3.2 永久解决方案

修改代码,把指定时区的代码放在第一行,参考下面代码

@SpringBootApplication
public class Application {
    
    public static void main(String[] args) {
        // 把时区设置提到第一行,防止类加载导致时区设置失败;
        TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("GMT+8")));
        // ParserConfig会导致JSON类提取加载,会执行JSON.defaultTimeZone的初始化,
        // 所以必须要先把系统时区设置正确
        ParserConfig.getGlobalInstance().setSafeMode(true);
        SpringApplication.run(Application.class, args);
    }

}

标签:服务,记录,--,夏令时,TimeZone,时区,public,GMT
From: https://www.cnblogs.com/zeng1994/p/3f8955568d67d41911855ca58e546297.html

相关文章

  • 【React工作记录六十四】ant design中rowKey的作用
     目录前言导语核心代码总结前言我是歌谣我有个兄弟巅峰的时候排名c站总榜19叫前端小歌谣曾经我花了三年的时间创作了他现在我要用五年的时间超越他今天又是接近兄弟的一天人生难免坎坷大不了从头再来歌谣的意志是永恒的放弃很容易但是坚持一定很酷导语antdesign中rowke......
  • 【React工作记录六十五】ant design子组件渲染不能及时渲染
     目录前言导语核心代码总结前言我是歌谣我有个兄弟巅峰的时候排名c站总榜19叫前端小歌谣曾经我花了三年的时间创作了他现在我要用五年的时间超越他今天又是接近兄弟的一天人生难免坎坷大不了从头再来歌谣的意志是永恒的放弃很容易但是坚持一定很酷导语antdesign子组件......
  • YOLOv5s训练学习记录 - test
    armorFinder_ROS2YOLOv5s训练学习记录:训练、获取模型YOLOv5初级使用教程下载yolov5git上下载Yolov5并调通测试代码数据集准备与文件夹结构这里我使用的是之前提供的装甲板数据集'RM_train_data'这个和原本提供的'RM_train_data'不太一样,需要改成下边的样子├─images│......
  • 做题记录(5.21~5.27)
    5.21口胡了UOJEasyRound1,想了大约20minUOJ12考虑到\(a=a_1g,b=b_1g\),那么\(gl=ab=a_1b_1g^2\),因此\(g|l\),设\(l=l_1g\),则有\(a_1b_1=l_1\),而\(a+b=g(a_1+b_1)\),显然\(a_1+b_1\)最大值是\(1+l_1\),最小值是\(2\sqrt{l_1}\)(\(l_1\)也是一个完全平方数)则\(a+b\)......
  • C语言学习记录04
    逻辑操作符:条件操作符||三目操作符:例://i>j成立,为真,所以i为真,j为假,所以结果为i。逗号表达式:下表引用操作符:函数调用操作符:常见关键字:命名规则:......
  • 【踩坑记录】autojs使用while(1)导致broadcast无法正常执行
    autojs中的死循环操作最好使用setInterval而不是,while(1)。 while(1)会导致其他语句无法执行,这里面包括了信号相关的,比如今天踩得坑: 用events.broadcast.emit发出信号后,相应的on语句无法正常执行,后来才发现原来是为了一个用while(1)来执行死循环导致整个线程全部死在了这里,......
  • 记录一下SOCKET编程
    记录一下基本的socket编程首先贴几段代码centos下的server代码#include<bits/stdc++.h>#include<unistd.h>#include<arpa/inet.h>#include<sys/socket.h>usingnamespacestd;intmain(){intserver,client;structsockaddr_inserverAddr,clientAddr......
  • 记录--按钮级别权限怎么控制
    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助最近的面试中有一个面试官问我按钮级别的权限怎么控制,我说直接v-if啊,他说不够好,我说我们项目中按钮级别的权限控制情况不多,所以v-if就够了,他说不够通用,最后他对我的评价是做过很多东西,但是都不够深入,好吧,那今天我们......
  • 怎样记录待办事项?怎么制定一个优质的待办清单?
    无论是在生活还是工作中,我们总是有各种各样的待办事项需要完成。如果没有一个有效的方式来记录和管理这些待办事项,很容易就会遗漏或忘记一些重要的事情,从而造成不良的后果。于是怎么记录待办事项?怎么制定一个优质的待办清单?是很多人都需要去面对的问题。其实想要制定一个优质、高......
  • 博学谷学习记录 自我总结 用心分享 | Alibaba- GateWay
    SpringCloudNetflix项目进入维护模式,SpringCloudNetflix将不再开发新的组件,我们知道SpringCloud版本迭代算是比较快的,因而出现了很多中岛的ISSUE都来不及Fix就又推另一个Release了。进入维护模式意思就是目前已知以后一段时间SpringCloudNetflix提供的服......