Orion's Studio.

跨域关于计算相差天数的两三事

2019/12/25

前言

因为有个需求要展示活动的第几天,以及根据这个天数回查历史记录,所以要写一个函数计算两个日期相差的天数。

一般想到计算两个时间的差值,肯定就是转成时间戳然后相减,最后除以 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
2
3
4
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);

这段代码还有更精简的写法

1
date.setHours(0, 0, 0, 0);

这样处理后直接除以 86400000 就变成相对于 Unix 元年元天经过的天数了。

此外,在处理时间时,参数常常在时间字符串和时间戳之间飘忽不定,用 new Date() 接收参数可以中和这两种可能性。

如下就是最终的代码:

1
2
3
4
const diffDay = (...aDates) =>
aDates
.map(aDate => Math.floor(new Date(aDate).setHours(0, 0, 0, 0) / 86400000))
.reduce((prev, cur) => prev - cur);

最后写了测试用例在本地时间测试没问题就上线了。

再起波澜

上线跑了一阵子都没啥问题,然而好景不长,有一天,一位纽约的用户来反馈她获取不到数据。

仔细排查了一下,定位到问题是计算的天数不对,原来是我没有考虑到时区的影响。

我们在东八区,而这位用户在西五区,相差 13 个小时,也就是我们在用户出问题的晚上 20 点的时候,这位用户应该在纽约的早上 7 点。

但这并没有问题啊,不还是在同一天???

我又仔细想了下才整明白,出问题的并不是当前的时间,而是活动开始的时间,原本在 0 点开始的活动,变成了在前一天上午 11 点开始。

所以用户从北京时间 13 点开始拿到的相差天数总比实际值多 1,而多了一天的活动此时还未开始,以至于没获取到数据。

解决方案

问题产生的根源在于不同时区 new 出来的 Date 都不同,于是乎我们只要把时间都统一到北京时间就行了。

但我也没寻觅到能直接用北京时间初始化的参数,那我们只能自己拨动纽约的时针表到和北京时间一样了。

于是就想到用 getTimezoneOffset 来获取当前时间的偏移,再减去东八区的偏移,差值就是慢的分钟数了。

差值也就是 new Date().getTimezoneOffset() - (-480),所以改造后的代码如下:

1
2
3
4
5
6
7
const diffDay = (...aDates) =>
aDates
.map((aDate) => {
const diff = (new Date().getTimezoneOffset() + 480) * 60000;
return Math.floor(new Date(+new Date(aDate) + diff).setHours(0, 0, 0, 0) / 86400000);
})
.reduce((prev, cur) => prev - cur);

但是改造完之后居然有个用例跑不过了,即计算某个北京时间 23:59:59 和下一天 00:00:00 的时间戳,如下:

1
diffDay(1572451199000, 1572451200000);

理论上结果应该是 -1,但结果是 0,究竟是怎么回事呢?

这时候我了解到了一个新概念————夏令时。

夏令时

夏令时(Daylight Saving Time:DST),又称“日光节约时制”和“夏令时间”,是一种为节约能源而人为规定地方时间的制度,在这一制度实行期间所采用的统一时间称为“夏令时间”。一般在天亮早的夏季人为将时间调快一小时,可以使人早起早睡,减少照明量,以充分利用光照资源,从而节约照明用电。各个采纳夏时制的国家具体规定不同。目前全世界有近110个国家每年要实行夏令时。

美国的夏令时从3月的第2个周日开始到11月的第1个周日结束。

我们可以打印下时间看看:

1
2
3
4
new Date();
// Tue Dec 24 2019 11:47:55 GMT-0500 (北美东部标准时间)
new Date(1572451199000);
// Wed Oct 30 2019 11:59:59 GMT-0400 (北美东部夏令时间)

可以看见原先为西五区的纽约在夏天变成了西四区,也就是说夏天美国的时间都变快了一个小时。

而之前的北京时间 23:59:59 和下一天 00:00:00 的时间戳的转化,其实是减了 12 小时却仍然增加了 13 小时,以至于变成了 00:59:59 和 01:00:00 的比较,所以结果是 0 而非 -1;

平息波澜

那怎么解决这个问题呢?

其实怪我自己阅读 API 不仔细:

getTimezoneOffset() 方法返回的是本地时间与 GMT 时间或 UTC 时间之间相差的分钟数。实际上,该函数告诉我们运行 JavaScript 代码的时区,以及指定的时间是否是夏令时。

其实 getTimezoneOffset 是根据 Date 对象的时间来获取差值的,还是用上文的两个时间:

1
2
new Date().getTimezoneOffset(); // 300
new Date(1572451199000).getTimezoneOffset(); // 240

可以看到在我写这篇文章的冬天相差还是 5 个小时,跑到夏天就变成 4 个小时了。

所以最后终于破案了,只要在 new Date() 的时候传入对应时间 new Date(aDate) 就行了:

1
2
3
4
5
6
7
const diffDay = (...aDates) =>
aDates
.map((aDate) => {
const diff = (new Date(aDate).getTimezoneOffset() + 480) * 60000;
return Math.floor(new Date(+new Date(aDate) + diff).setHours(0, 0, 0, 0) / 86400000);
})
.reduce((prev, cur) => prev - cur);

尾声

想说时间后台处理不好吗!!! 前端何必为难自己???

参考

MDN

W3School

百度知道

CATALOG
  1. 1. 前言
  2. 2. 再起波澜
  3. 3. 解决方案
  4. 4. 夏令时
  5. 5. 平息波澜
  6. 6. 尾声
  7. 7. 参考