Vol.6 一个时区问题引发的加班
一、事情是这样的
在一个平凡的下午,刚发完版本的我想着:今天真是完美的一天,版本顺顺利利,今晚可以早点回家睡觉了。这时,不出意外就是要出意外了。线上收到反馈,一位同事去了美国出差,在创建活动的时候发现:创建出来的活动开始时间竟然比设置的晚15个小时。当然第一时间就让这位同事暂停活动,并且切换中国时区重新新建,线上先恢复。
但是这个问题还是得修,怎么办,看呗。
二、现状是怎么样的
在页面上有一个这样的输入框,要求用户选择UTF+8的时间:
拿到选择的字符串后,我们就会使用new Date().getTime()获取时间戳传递给后台:
let dateStr = '2024/07/06 10:00:00'
let localTimeStamp = new Date(dateStr).getTime() // 传递给后台
三、问题分析
我们先来看看时间戳的定义:
时间戳(TimeStamp)是用来表示特定时间点的数值。它通常是一个整数,表示从某个时间点(通常是1970年1月1日00:00:00 UTC,即Unix纪元)开始经过的秒数或毫秒数。
那为什么这里有时区问题呢?给到后台的不都是时间戳吗?
时间戳本身是没有时区概念的,但是用户选择的字符串和JS的API是隐含了时区特性的。
我们看下这里的交互:
此时,用户选择的字符串其实期望的是UTC+8的时间,无论在任何国家,都是需要选择北京时间的。
拿到业务含义上的UTC+8的字符串后,我们由于需要转换为时间戳,此时我们会调用API(new Date(dateStr).getTime()),这个调用API的步骤就出了问题:
我们发现两次执行的结果完全不同,他们之间的差值:(1720285200000 - 1720231200000)/ (60 * 60 * 1000) = 15。正好就是15个小时。
不同的时区,相同的api,相同的入参,返回结果完全不一样。这个时间戳传给后台,一定是会有问题的。
我们再看看下面的执行结果:
由于中国的时间戳为0的节点是从1970/01/01 08:00:00开始的,而美国的0节点是从1969/12/31 16:00:00,因此同一个时间字符串(例如1970/01/01 08:00:00),中国的时间戳一定会比美国的时间戳小15个小时。
这里可能会讲,明明+8和-8是16个小时时差,为什么实际计算是15个小时呢?这是因为,这篇文章正好是夏天写的,此时还有个夏时令的东西,实际此时美国是UTC-7。
同一时间字符串,为什么中国时间戳比美国小:因为时间戳是对0的偏移量,由于中国的时间是从1970/01/01 08:00:00为原点的,而美国是以更早的时间为原点,那么中国到同一个时间的毫秒数,就会比美国小。
四、问题解决
经过上面的分析,这里问题很明确了,我们只需要把字符串,转换成为UTC+8对应的时间戳。算法如下:
设定如下:
- n: 为当地时间便宜为UTC0的偏移量,如中国为-8,美国为+7 (new Date().getTimezoneOffset() / 60 )
- str:用户输入的字符串
let localTimeStamp = new Date(dateStr).getTime() // 此时获取的时间戳是当地时间的时间戳,隐含了UTC+N
let utc0Stamp = localTimeStamp - n * 1000 * 60 * 60 // 当前字符串,对应utc0的时间戳
let utc8Stamp = utc0Stamp + (-8 * 1000 * 60 * 60) // 当前字符串,对应utc8的时间戳
优化一下:
let gap = gapUtc8 = (-8-n) * 1000 * 60 * 60
le utc8Stamp = new Date(dateStr).getTime() + gapUtc8
五、总结
因为这个问题,美好的两天无了。orz
转载自:https://juejin.cn/post/7393522719080841255