当前位置:网站首页>会员生日提前了一天
会员生日提前了一天
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
边栏推荐
猜你喜欢

【Untitled】

HCIP第十六天笔记

【LeetCode】70. 爬楼梯 - Go 语言题解

$\text{ARC 145}$
![[MySQL] Mysql transaction and authority management](/img/a5/c92e0404c6a970a62595bc7a3b68cd.gif)
[MySQL] Mysql transaction and authority management

【Untitled】

Golang go-redis cluster模式下不断创建新连接,效率下降问题解决

IJCAI2022 Tutorial | Spoken Language Comprehension: Recent Advances and New Fields

Introducing the visualization tool Netron

Detailed operator
随机推荐
【Untitled】
Lambda表达式
EasyExcel comprehensive course combat
DFS question list and template summary
"NIO Cup" 2022 Nioke Summer Multi-School Training Camp 2 H.Take the Elevator
“蔚来杯“2022牛客暑期多校训练营4 DHKLN
【Untitled】
[SAM模板题] P3975 [TJOI2015] 弦论
Go1.18升级功能 - 模糊测试Fuzz 从零开始Go语言
[MySQL] DQL related operations
"Code execution cannot continue because MSVCP140.dll was not found, reinstalling the program may resolve the problem, etc." Solutions
leetcode:127. 单词接龙
CPM:A large-scale generative chinese pre-trained lanuage model
2022中国物流产业大会暨企业家高峰论坛在杭州举办!
2022.7.28
Apache Doris series: In-depth understanding of real-time analytical database Apache Doris
leetcode(刷题篇13)
[MySQL] Related operations on databases and tables in MySQL
软考总结
打动中产精英群体,全新红旗H5用产品力跑赢需求