问题:
中国从1986年到1991年的六个年度曾实施过夏令时,在夏令时区间,Asia/Shanghai和GMT+8这两种不同的时区格式表示同一个时间时,会有1个小时的误差。
原因分析:
1、时区信息配置不一致导致。比如linux服务器的默认时区,K8S容器配置的默认时区,数据库设置的默认时区等。比如服务器的时区为:‘Asia/Shanghai’,而在jdbcUrl中指定的数据库使用的时区是GMT+8
2、String和Date 互转时,指定的时区不一致导致。GMT+8 因为没有位置信息,所以无法使用夏令时;而Asia/Shanghai 这种时区却使用了夏令时(详情可参考:https://alphahinex.github.io/2021/10/31/difference-between-gmt-plus-8-and-asia-shanghai/)
过程复现:
我们先用Asia/Shanghai把String反序列化成Date,然后再用GMT+8序列化回String,最终产生1小时的时差。
比如:定义如下UserDTO ,包含日期类型的字段:生日
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birthDay;
假定传递birthDay的值为:1990-06-13。
将json串反序列化为对象,逻辑如下:
UserPO userPO = JSON.parseObject(userStr,UserPO.class);
此时可以看到。birthDay的日期为CDT格式。具体如下:
携带时区信息如下:
此时,如果数据库配置的时区为GMT-8,则此数据落库时,就会造成日期少一天。
而如果我们的工程里,配置默认时区为:GMT-8,则同样反序列化,看到效果是:
所以,对于日期字段和String类型互转时,要特别注意夏令时的问题。
我们还可以延伸到其他场景:
1.从mongodb查询数据时。一般来说,mongodb 底层默认统一使用UTC时间,但转换到java对象时,使用了LocalDate类型,如果时区没有统一配置,也可能出现夏令时问题。
2.使用SimpleDateFormat 进行日期转换时。
代码示例:
public static Date parse2Date(String str, String pattern) {
SimpleDateFormat format = new SimpleDateFormat(pattern);
// format.setTimeZone(TimeZone.getTimeZone("GMT+8"));
try {
return format.parse(str);
} catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
}
public static String parse2Str(Date date , String pattern) {
SimpleDateFormat format = new SimpleDateFormat(pattern);
// format.setTimeZone(TimeZone.getTimeZone("GMT+8"));
try {
return format.format(date);
} catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
}
总结:
开发代码时,接口的出入参、日期转换时,如果所有的时区配置都遵从一种标准,则无论是系统内部日期转换还是上下游的系统调用,夏令时问题将不复存在。
比如,统一加入如下配置类:
@Configuration
public class GlobalZoneConfig {
@PostConstruct
public void init(){
TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));
}
}
最佳实践:
整个研发团队采用统一的规范,比如:
1.接口层返回出参,如果有日期类型,建议统一为String类型(无论提供给其他微服务、还是前端)
2.接口层定义入参的日期格式(yyyy-MM-dd)时,建议为String类型(保证入口的参数是正确的日期,即不存在多一天或者少一天的情况)。
3.推荐统一用一种类型接收db数据(要么是String类型,要么是Date类型)。
标签:String,format,解决方案,夏令时,最佳,Date,时区,GMT From: https://blog.csdn.net/liuyatao_/article/details/143863440