当前位置:网站首页>会员生日提前了一天
会员生日提前了一天
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
边栏推荐
猜你喜欢
2022中国物流产业大会暨企业家高峰论坛在杭州举办!
HCIP第十五天笔记
VS2017 compile Tars test project
Chevrolet Trailblazer, the first choice for safety and warmth for your family travel
【MySQL】MySQL中对数据库及表的相关操作
# # yyds dry goods inventory interview will brush TOP101: to determine whether there is a part of the list
HCIP Day 15 Notes
【LeetCode】70. 爬楼梯 - Go 语言题解
reindex win10
智能创意中的尺寸拓展模块
随机推荐
Day016 Classes and Objects
Gxlcms audio novel system/novel listening system source code
vscode上利用screen命令跑代码
递增三元组
第一节 zadig 入门
StoneDB 为何敢称业界唯一开源的 MySQL 原生 HTAP 数据库?
DFS question list and template summary
uniapp折叠框二级循环
mysql获取近7天,7周,7月,7年日期,根据当前时间获取近7天,7周,7月,7年日期
阿里云视频点播+项目实战
Apache Doris系列之:安装与部署详细步骤
Flex布局使用
$\text{ARC 145}$
go版本升级
Py's pdpbox: a detailed introduction to pdpbox, installation, and case application
el-upload添加请求头
"Wei cup" school more than 2022 cattle summer camp 4 L.B lack Hole, computational geometry
Kotlin特殊类
[MySQL] Mysql transaction and authority management
ZZULIOJ:1119: sequence order