海外业务时间处理

背景

海外浏览器后台有个业务配置是在一定的时间段内生效的,从数据库里获取生效的配置使用如下的 sql 语句

-- 查询出当前生效的视频精选
SELECT id FROM video_choice WHERE
    status=1 AND
    effective_time<=NOW() AND expiration_time>=NOW()

测试通过以后,发布到灰度环境进行验证,测试同学反馈不能获取当前生效的视频精选

原因

先 review 代码,没有头绪;后来发现是生产环境的时间设置问题,如下

可以看到数据库服务器返回的时间,以及业务服务器时间和办公电脑的时间不一致——排除一些操作上的时间差,生产环境机房时间和办公环境(也就是北京时间)相差 7 个小时

解决时间不一致问题

修改时区设置

首先是想到找 DBA,把数据库时间设置为北京时间,但是被拒绝了

虽然香港也是北京时间,但是用 UTC 貌似也很合理,问题是——北京时间不是和 UTC 时间相差 8 个小时吗?

这个多出来的一小时只能用夏令时来解释了......虽然香港根本就不采用夏令时

自己做时区转换

既然机房的时间不能改,那只能自己想办法解决了

最简单的就是直接把 UTC 时间转换为北京时间,但是香港机房这个诡异的夏令时怎么处理?将来在其他国家/地区的机房部署程序,又该如何处理?非常麻烦,搞了半天,没有解决,反而觉得越来越复杂了

出乎意料的简单解决方案

网上查了一下,可以在程序启动时传一个时区参数,如下

不过,这要修改 jetty 或 launcher 程序启动的脚本,恐怕运维同学不会同意

.

.

.

可以在 java 程序启动后第一时间设置 user.timezone 环境变量,如下

使用 Spring 的注解自动装配的话,只要定义一个类,不用侵入任何代码

注意 @Order 注解,它保证了在你的业务代码执行前时区已被设置

这样,无论你的机房在哪个大洲的哪个国家/地区,也无论其时区如何设置,是否采用夏令时,都可以用北京时间来处理时间了

后记

修改时区并生效

最近一个项目又涉及到时间,发生了新情况:将用户输入的时间转为整数型的时间戳保存到数据库,测试同学发现该时间戳再转换为北京时间后和原输入时间不相等。经过研究后发现,执行如下代码实际上时区调整并没有生效~!

可以用一个例子来测试一下

输出为

也就是说,虽然修改了时区的系统变量,但是并不会导致时区的变更,除非执行 TimeZone.setDefault(null) 来强制 java 重新计算时区

freemarker 的时间格式化

强制时区切换后,又有问题了:用户进入修改界面,时间又变回机房的香港时间,而不是输入时的北京时间了,经过研究,发现页面上展示时间是用的 freemarker 的时间格式化功能,代码如下

看来是 freemarker 使用的时区依然是香港机房本地设置,并没有被我们修改时区的代码影响到,那么我们可以通过设置 freemarker 的时区来解决这个问题,在 spring 配置文件里如下配置 freemarker 即可

注意上面代码里 16,17 行

结论

要想在不同时区的机房里使用北京时间,只需要修改时区,如下

此外,如果你用到了 freemarker,那你需要配置 freemarker 的时区,方法是设置其 time_zone 参数,以 spring 整合 freemarker 来示例

Last updated