当前位置:网站首页>会员生日提前了一天
会员生日提前了一天
2022-07-30 23:18:00 【M_O_】
背景
有一天,收到反馈,某些用户的生日提前了一天(变成了前一天的23:00:00), 比如填写生日"1988-08-20",数据库中变成了"1988-08-19 23:00:00"
创建容器的时候,指定了系统时区为Asia/Shanghai
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
数据库连接指定了连接时区为GMT+8(原本也是Asia/Shanghai,这是为了修复另外一个问题)
spring.datasource.url=jdbc:mysql://192.168.1.10:3344/user?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
原因探查
一般这种情况是时区原因导致,直接搜索一下Asia/Shanghai,发现Asia/Shanghai在1986~1991年使用了夏令时。
中国夏令时
1986年4月,中国中央有关部门发出“在全国范围内实行夏时制的通知”,具体做法是:每年从四月中旬第一个星期日的凌晨2时整(北京时间),将时钟拨快一小时,即将表针由2时拨至3时,夏令时开始;到九月中旬第一个星期日的凌晨2时整(北京夏令时),再将时钟拨回一小时,即将表针由2时拨至1时,夏令时结束。从1986年到1991年的六个年度,除1986年因是实行夏时制的第一年,从5月4日开始到9月14日结束外,其它年份均按规定的时段施行。在夏令时开始和结束前几天,新闻媒体均刊登有关部门的通告。1992年起,夏令时暂停实行。
PS:据说是为了省电,但实际毛线也没有省,却给我们程序员挖了坑。
解析生日输入的代码如下
public static Date parseBirthdayDate(String date) {
return new SimpleDateFormat("yyyy-MM-dd").parse(date);
这里使用Asia/Shanghai解析时间,由于夏令时的原因,这里得到的时间戳会少一个小时
那么是不是保存到数据库中后,数据库(时区为UTC+08:00)将时间转换为自己时区的时间导致出现问题呢?
数据库使用了datetime类型来保存时间
mysql datetime:
A date and time combination. The supported range is ‘1000-01-01 00:00:00.000000’ to ‘9999-12-31 23:59:59.999999’. MySQL displays DATETIME values in ‘YYYY-MM-DD hh:mm:ss[.fraction]’ format, but permits assignment of values to DATETIME columns using either strings or numbers.
MySQL converts TIMESTAMP values from the current time zone to UTC for storage, and back from UTC to the current time zone for retrieval. (This does not occur for other types such as DATETIME.)
由此可见datetime不会因为时区而进行转换,问题不是在这里。
程序使用了mybatis框架保存数据到数据库,生日是转换为Date类型后保存的,看一下类型转换器:
SqlDateTypeHandler:
public class SqlDateTypeHandler extends BaseTypeHandler<Date> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType)
throws SQLException {
ps.setDate(i, parameter);
}
//....
}
public class PreparedStatement {
public void setDate(int parameterIndex, java.sql.Date x) throws java.sql.SQLException {
synchronized (checkClosed().getConnectionMutex()) {
setDateInternal(parameterIndex, x, this.session.getDefaultTimeZone());
}
}
private void setDateInternal(int parameterIndex, Date x, TimeZone tz) throws SQLException {
if (x == null) {
setNull(parameterIndex, MysqlType.DATE);
} else {
if (this.ddf == null) {
this.ddf = new SimpleDateFormat("''yyyy-MM-dd''", Locale.US);
}
this.ddf.setTimeZone(tz);
setInternal(parameterIndex, this.ddf.format(x));
}
}
}
这里可以看到,Date数据在转换时,是调用了new SimpleDateFormat(“‘‘yyyy-MM-dd’’”), 并且使用时区this.session.getDefaultTimeZone()格式化成字符串后保存的。
this.session.getDefaultTimeZone() 可以由连接参数serverTimezone指定。
因为上面数据库连接串指定了使用GMT+8, 所以在转换后,字符串时间将会少一个小时!
解决问题
大部分时候夏令时只会带来问题,因此解决方案是将系统时间修改为GMT+8,不再采用Asia/Shanghai时区。
然后发现,时区数据库tzdata中没有Asia/Beijing, 我国位于东8时区的地区还有Asia/Hong_Kong和Asia/Taipei,但这两个一样有夏令时的问题。
最后确定采用Etc/GMT-8时区来作为系统时区,为什么是GMT-8而不是GMT+8,可以参考维基百科说明:
The special area of “Etc” is used for some administrative zones, particularly for “Etc/UTC” which represents Coordinated Universal Time. In order to conform with the POSIX style, those zone names beginning with “Etc/GMT” have their sign reversed from the standard ISO 8601 convention. In the “Etc” area, zones west of GMT have a positive sign and those east have a negative sign in their name (e.g “Etc/GMT-14” is 14 hours ahead of GMT).
修改Dockerfile为
ENV TZ=Etc/GMT-8
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
参考文章
[1] 夏令时
[2] The DATE, DATETIME, and TIMESTAMP Types
[3] tz database
边栏推荐
猜你喜欢

PyTorch model export to ONNX file example (LeNet-5)

Go1.18升级功能 - 泛型 从零开始Go语言

Apache Doris系列之:深入认识实时分析型数据库Apache Doris

Apache Doris series: In-depth understanding of real-time analytical database Apache Doris

一文详解:SRv6 Policy模型、算路及引流

Go语学习笔记 - gorm使用 - 事务操作 Web框架Gin(十一)

Apache Doris series: detailed steps for installation and deployment

# # yyds dry goods inventory interview will brush TOP101: to determine whether there is a part of the list

【MySQL】MySQL中对数据库及表的相关操作

Gxlcms audio novel system/novel listening system source code
随机推荐
HashSet源码解析
Compressing Deep Graph Neural Networks via Adversarial Knowledge Distillation
Go1.18升级功能 - 模糊测试Fuzz 从零开始Go语言
反转链表-头插反转法
HF2022-EzPHP复现
“蔚来杯“2022牛客暑期多校训练营4 L.Black Hole 垃圾计算几何
Mysql进阶优化篇01——四万字详解数据库性能分析工具(深入、全面、详细,收藏备用)
Apache Doris series: detailed steps for installation and deployment
实验7(MPLS实验)
Excel基础学习笔记
只会纯硬件,让我有点慌
2021GDCPC Guangdong University Student Programming Competition H.History
雪佛兰开拓者,安全保障温暖你的家庭出行的第一选择
oracle数据库版本问题咨询(就是对比从数据库查询出来的版本,和docker里面的oracle版本)?
Achievements of Science and Technology (31)
“由于找不到MSVCP140.dll,无法继续执行代码,重新安装程序可能会解决此问题等”解决方案
数据清洗-使用es的ingest
DFS question list and template summary
10 个关于自动化发布管理的好处
proemthues 服务发现配置