前言
因为有个需求要展示活动的第几天,以及根据这个天数回查历史记录,所以要写一个函数计算两个日期相差的天数。
一般想到计算两个时间的差值,肯定就是转成时间戳然后相减,最后除以 86400000 就完事了嘛~
这在相隔时间超过 1 天的情况下基本都是没问题的,但是如果相差时间在同一天内,就没法直接通过差值来判断相差天数。
比如说 23:59:59 和 00:00:00 是相差 1 秒,00:00:00 和 00:00:01 也是相差 1 秒,但前者相隔了一天,而后者却是同一天。
产生这个问题的根源其实在于对于每一天的偏移量不同,即 23:59:59 几乎偏移了一整天,而 00:00:01 只偏移了 1 秒,如果把这部分偏移量去除干净,那比较的就是绝对的天数之差了。
所以最简单的思路就是把每个时间处理为那天 00:00:00 时的时间戳,也就是如下的代码:
1 | date.setHours(0); |
这段代码还有更精简的写法
1 | date.setHours(0, 0, 0, 0); |
这样处理后直接除以 86400000 就变成相对于 Unix 元年元天经过的天数了。
此外,在处理时间时,参数常常在时间字符串和时间戳之间飘忽不定,用 new Date()
接收参数可以中和这两种可能性。
如下就是最终的代码:
1 | const diffDay = (...aDates) => |
最后写了测试用例在本地时间测试没问题就上线了。
再起波澜
上线跑了一阵子都没啥问题,然而好景不长,有一天,一位纽约的用户来反馈她获取不到数据。
仔细排查了一下,定位到问题是计算的天数不对,原来是我没有考虑到时区的影响。
我们在东八区,而这位用户在西五区,相差 13 个小时,也就是我们在用户出问题的晚上 20 点的时候,这位用户应该在纽约的早上 7 点。
但这并没有问题啊,不还是在同一天???
我又仔细想了下才整明白,出问题的并不是当前的时间,而是活动开始的时间,原本在 0 点开始的活动,变成了在前一天上午 11 点开始。
所以用户从北京时间 13 点开始拿到的相差天数总比实际值多 1,而多了一天的活动此时还未开始,以至于没获取到数据。
解决方案
问题产生的根源在于不同时区 new 出来的 Date 都不同,于是乎我们只要把时间都统一到北京时间就行了。
但我也没寻觅到能直接用北京时间初始化的参数,那我们只能自己拨动纽约的时针表到和北京时间一样了。
于是就想到用 getTimezoneOffset
来获取当前时间的偏移,再减去东八区的偏移,差值就是慢的分钟数了。
差值也就是 new Date().getTimezoneOffset() - (-480)
,所以改造后的代码如下:
1 | const diffDay = (...aDates) => |
但是改造完之后居然有个用例跑不过了,即计算某个北京时间 23:59:59 和下一天 00:00:00 的时间戳,如下:
1 | diffDay(1572451199000, 1572451200000); |
理论上结果应该是 -1,但结果是 0,究竟是怎么回事呢?
这时候我了解到了一个新概念————夏令时。
夏令时
夏令时(Daylight Saving Time:DST),又称“日光节约时制”和“夏令时间”,是一种为节约能源而人为规定地方时间的制度,在这一制度实行期间所采用的统一时间称为“夏令时间”。一般在天亮早的夏季人为将时间调快一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。各个采纳夏时制的国家具体规定不同。目前全世界有近110个国家每年要实行夏令时。
美国的夏令时从3月的第2个周日开始到11月的第1个周日结束。
我们可以打印下时间看看:
1 | new Date(); |
可以看见原先为西五区的纽约在夏天变成了西四区,也就是说夏天美国的时间都变快了一个小时。
而之前的北京时间 23:59:59 和下一天 00:00:00 的时间戳的转化,其实是减了 12 小时却仍然增加了 13 小时,以至于变成了 00:59:59 和 01:00:00 的比较,所以结果是 0 而非 -1;
平息波澜
那怎么解决这个问题呢?
其实怪我自己阅读 API 不仔细:
getTimezoneOffset() 方法返回的是本地时间与 GMT 时间或 UTC 时间之间相差的分钟数。实际上,该函数告诉我们运行 JavaScript 代码的时区,以及指定的时间是否是夏令时。
其实 getTimezoneOffset
是根据 Date 对象的时间来获取差值的,还是用上文的两个时间:
1 | new Date().getTimezoneOffset(); // 300 |
可以看到在我写这篇文章的冬天相差还是 5 个小时,跑到夏天就变成 4 个小时了。
所以最后终于破案了,只要在 new Date()
的时候传入对应时间 new Date(aDate)
就行了:
1 | const diffDay = (...aDates) => |
尾声
想说时间后台处理不好吗!!! 前端何必为难自己???