首页 >> 大全

一次因JDK夏令时导致接口输出日期格式的时间与预期时间不一致的bug排查总结

2023-11-14 大全 30 作者:考证青年

环境说明

开始bug排查之前,先说明下项目环境:

bug 排查 从数据层开始查找,先查询数据库时间和时区

SQL> SELECT SYSTIMESTAMP, SESSIONTIMEZONE FROM DUAL;
SYSTIMESTAMP                                                                     SESSIONTIMEZONE
-------------------------------------------------------------------------------- ---------------------------------------------------------------------------
17-JUL-19 02.20.06.687149 PM +08:00                                              +08:00SQL>

数据库时间和时区都没有问题。

确认操作系统和java进程时区

[test@test ~]$ date -R
Wed, 17 Jul 2019 16:48:32 +0800
[test@test ~]$ cat /etc/timezone
Asia/Shanghai

[test@test ~]$ jinfo 7490 |grep user.timezone
user.timezone = Asia/Shanghai

可以看出我们操作系统使用的时区和java进程使用的时区一致,都是东八区。

用debug继续往上层查找查看和JDBC层

查看了问题字段映射字段的类型为="",在中类型处理注册类.java 中对应的处理类为 .java。

this.register((JdbcType)JdbcType.TIMESTAMP, (TypeHandler)(new DateTypeHandler()));

进一步查看 .java 类:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//package org.apache.ibatis.type;import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;public class DateTypeHandler extends BaseTypeHandler {public DateTypeHandler() {}public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {ps.setTimestamp(i, new Timestamp(parameter.getTime()));}public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {Timestamp sqlTimestamp = rs.getTimestamp(columnName);return sqlTimestamp != null ? new Date(sqlTimestamp.getTime()) : null;}public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {Timestamp sqlTimestamp = rs.getTimestamp(columnIndex);return sqlTimestamp != null ? new Date(sqlTimestamp.getTime()) : null;}public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {Timestamp sqlTimestamp = cs.getTimestamp(columnIndex);return sqlTimestamp != null ? new Date(sqlTimestamp.getTime()) : null;}
}

因为使用的数据源为Druid,其中 ( rs, ) 方法参数中 使用了.java 的 ( ) ,通过列名称获取值然后转换为Date类型的值。

debugDate

由上图debug看到 是JDK中的类,也就是说这里看到的是JDK使用的时间和时区,从图中标注2处可以看出JDK使用的时区也是东八区,但是从1和3处看起来似乎有点不一样,首先1处变化为UTC/GMT+0900,3处有一个的这样一个时间,换算为小时刚好为1个小时。这个值通过搜索知道叫做夏令时。

常用时间概念 UTC,GMT,CST,DST

中国夏时制实施时间规定(夏令时) 1935年至1951年,每年5月1日至9月30日。 1952年3月1日至10月31日。 1953年至1954年,每年4月1日至10月31日。 1955年至1956年,每年5月1日至9月30日。 1957年至1959年,每年4月1日至9月30日。 1960年至1961年,每年6月1日至9月30日。 1974年至1975年,每年4月1日至10月31日。 1979年7月1日至9月30日。 1986年至1991年,每年4月中旬的第一个星期日1时起至9月中旬的第一个星期日1时止。具体如下: 1986年4月13日至9月14日, 1987年4月12日至9月13日, 1988年4月10日至9月11日, 1989年4月16日至9月17日, 1990年4月15日至9月16日, 1991年4月14日至9月15日。

_一次因JDK夏令时导致接口输出日期格式的时间与预期时间不一致的bug排查总结_一次因JDK夏令时导致接口输出日期格式的时间与预期时间不一致的bug排查总结

通过对比我们可以看到应用中的对应的用户生日"1988-07-29"刚好在中国的夏令时区间内,因为我们操作系统、数据库、JDK使用的都是 "Asia/" 时区,应该不会错,通过上图中debug结果我们也证实了结果是没问题的。

继续往外排查业务层和接口层,定位到问题

项目使用的是 boot提供rest接口返回json报文,使用 默认的框架解析。项目中有需要对外输出统一日期格式,对做了一下配置:

#jackson
#日期格式化
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

我们通过查看 .java源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//package org.springframework.boot.autoconfigure.jackson;import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.util.EnumMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties(prefix = "spring.jackson"
)
public class JacksonProperties {private String dateFormat;private String jodaDateTimeFormat;private String propertyNamingStrategy;private Map serialization = new EnumMap(SerializationFeature.class);private Map deserialization = new EnumMap(DeserializationFeature.class);private Map mapper = new EnumMap(MapperFeature.class);private Map parser = new EnumMap(Feature.class);private Map generator = new EnumMap(com.fasterxml.jackson.core.JsonGenerator.Feature.class);private Include defaultPropertyInclusion;private TimeZone timeZone = null;private Locale locale;public JacksonProperties() {}public String getDateFormat() {return this.dateFormat;}public void setDateFormat(String dateFormat) {this.dateFormat = dateFormat;}public String getJodaDateTimeFormat() {return this.jodaDateTimeFormat;}public void setJodaDateTimeFormat(String jodaDataTimeFormat) {this.jodaDateTimeFormat = jodaDataTimeFormat;}public String getPropertyNamingStrategy() {return this.propertyNamingStrategy;}public void setPropertyNamingStrategy(String propertyNamingStrategy) {this.propertyNamingStrategy = propertyNamingStrategy;}public Map getSerialization() {return this.serialization;}public Map getDeserialization() {return this.deserialization;}public Map getMapper() {return this.mapper;}public Map getParser() {return this.parser;}public Map getGenerator() {return this.generator;}public Include getDefaultPropertyInclusion() {return this.defaultPropertyInclusion;}public void setDefaultPropertyInclusion(Include defaultPropertyInclusion) {this.defaultPropertyInclusion = defaultPropertyInclusion;}public TimeZone getTimeZone() {return this.timeZone;}public void setTimeZone(TimeZone timeZone) {this.timeZone = timeZone;}public Locale getLocale() {return this.locale;}public void setLocale(Locale locale) {this.locale = locale;}
}

得知 ..time-zone 属性操作的就是java.util.。于是我们通过一段测试代码模拟转换过程:

package com.test;import java.sql.Date;
import java.util.TimeZone;/*** @author alexpdh* @date 2019/07/17*/
public class Test {public static void main(String[] args) {System.out.println("当前的默认时区为: " + TimeZone.getDefault().getID());Date date1 = Date.valueOf("1988-07-29");Date date2 = Date.valueOf("1983-07-29");System.out.println("在中国夏令时范围内的时间 date1=" + date1);System.out.println("正常东八区时间 date2=" + date2);
//		模拟 spring.jackson.time-zone=GMT+8 属性设置TimeZone zone = TimeZone.getTimeZone("GMT+8");TimeZone.setDefault(zone);System.out.println(TimeZone.getDefault().getID());Date date3 = date1;Date date4 = date2;System.out.println("转换后的在中国夏令时范围内的时间date3=" + date3);System.out.println("转换后的正常东八区时间 date4=" + date4);}
}

运行后输出结果:

当前的默认时区为: Asia/Shanghai
在中国夏令时范围内的时间 date1=1988-07-29
正常东八区时间 date2=1983-07-29
GMT+08:00
转换后的在中国夏令时范围内的时间date3=1988-07-28
转换后的正常东八区时间 date4=1983-07-29

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了