夏令时踩坑记录二
第三方数据上传,上传上来发现有一个人的出生日变成了 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")));
}
}
简单分析:
- 第一时间看该代码,好像没啥问题,init方法里面指定了时区统一用GMT+8
- 从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);
}
}
作者:zeng1994
出处:http://www.cnblogs.com/zeng1994/
本文版权归作者和博客园共有,欢迎转载!但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接!