如果你们的用户(client)和服务器(server)都在国内(没有时差),那么恭喜你,很幸运,基本上不会遇到时差的问题。但是如果你们的服务器部署在海外,那么欢迎客官里面请,不妨进来瞧一瞧。
时间点 与 时间段
时间点:指的是一个具体的时刻,如“现在几点了”或“活动何时开始”。 时间段:指的是一段持续的时间,比如“还需多久”或“已经过去多久”。
只要明确了这两个概念,基本上可以满足大部分日常和业务场景对时间的需求。
时区 与 时差
假如张三和李四在一起,张三问李四现在几点了,李四看了看手表,说:现在是晚上 8 点。OK,没啥问题,沟通顺畅。
但是如果张三在北京,李四在伦敦,张三打电话问李四现在几点了,李四同样看了看手表,说:现在是中午 12 点。张三说:诶,不对啊,现在天都黑了,怎么可能才中午 12 点呢?张三自己看了一下自己的手表,你胡说八道,现在明明是晚上 8 点。
我们知道张三没错,李四也没错。所以接下来我们需要引出一个叫做时区的概念。上面的描述更准确一点来说是:张三说的是东八区的晚上 8 点,李四说的是零时区的中午 12 点。但无论是 东八区的晚上 8 点 还是 零时区的中午 12 点,都指的是同一时刻,也就是此刻。
虽然是同一时刻(同时发生),但是由于地区不同,导致一个是晚上八点(20:00),一个是中午十二点(12:00),中间相差 8 小时,这就是时差。
那么就不能统一一下吗?大家都用 8 点或者 12 点。这里就要涉及到我们对时间点的概念了:在我们的认知里,大概早上七点,是太阳升起;而不是晚上七点,太阳才升起。这其实也就是就是我们所说的标准时间或者说时区时间,用于当地的日常生活和法律规定。
那么全球就不能统一做到太阳同一时刻升起,又同一时刻落下吗?如果能做到,那么我们就可以干掉时区的概念了。而对于了解地理或者了解一定常识的朋友都知道,这显然是不可能的。
我们知道此时此刻既可以用 东八区的晚上 8 点 来表示,又可以用 零时区的中午 12 点 来表示,也就是可以使用多个量来表示。那么,能不能就用一个量来表示此时此刻呢?
其实也简单,就是只要我们事先约定好一个时区,大家再把自己时区的时间换算成那个约定时区的时间,然后大家都用这个时间来交流,这样就能保证大家所说的时间是同一个时刻。而国际上大家默认约定的这个时区是零时区,也就是著名的格林尼治时间(近似 UTC)。
UTC、UNIX 与 ISO
接下来我们再讲一讲三个常用的时间格式:UTC、UNIX、ISO。
UTC(Universal Time Coordinated,协调世界时):我们可以将 UTC 理解成格林尼治时间(实际上有一点差异,但几乎可以忽略),也就是无论你在哪里,格林尼治那边,现在几点了。
UNIX 时间戳:我们可以理解成:两个 UTC 时间的差值,而起点是 1970 年 1 月 1 日零时零分零秒(UTC),终点是现在。也就是现在格林尼治那边的时间 - 格林尼治那边1970 年 1 月 1 日零时零分零秒那时的时间。
ISO 8601:ISO 8601 是国际标准化组织制定的日期和时间的表示方法,也就是我们常说的 YYYY-MM-DDTHH:mm:ss.sssZ 这种格式。这种格式里既记录了时间,又记录了时区,比如这里的 Z 就表示零时区,而 +08:00 则表示东八区。
实际场景
时差 + 时间段
而当时差结合时间段一起计算时,就容易晕。不过只要把握:先转时差再做差 这条核心,相信难度也不大了。举个例子:
假如我们的服务器部署在英国伦敦,并且在伦敦 2025-02-25 08:00:00 建立一个订单。那么当我 2025-02-27 08:00:00 在中国上海查看这个订单时,我看到的信息应该是:这个单是 2025-02-25 16:00:00 创建,或者说 2025-02-25 08:00:00(伦敦) 这样也行,距离现在过了 一天零十六小时。
而当我 2025-02-29 08:00:00 在美国纽约查看这个订单时,我看到的信息应该是:这个单是 2025-02-25 03:00:00 创建,或者说 2025-02-25 08:00:00(伦敦),距离现在过了 四天零五小时。
为什么 new Date() 不等于 moment().format('YYYY-MM-DD HH:mm:ss') 了?
我们都知道 moment() 如果我们不传参数的话,moment 默认会使用当前时间,而 new Date() 不传也是默认使用当前时间。
假如我们的日志记录了两个字段,初略来看,这两个字段理论上要得是同一个时间:
{
date1: new Date().toISOString(),
date2: moment().format('YYYY-MM-DD HH:mm:ss')
}
现在我们通过查询日志,
// date1: 2025-02-21T12:08:01.670Z
// date2: 2025-02-21 12:08:01
接着我们在本地使用 moment(date1).format('YYYY-MM-DD HH:mm:ss') 转换一下 date1,发现结果是 2025-02-21 20:08:01,居然和 date2 不一样。
这其实就是典型的时区信息丢失问题。首先我们来讲一讲 moment:
moment 在创建时间时,如果参数中有时区信息就使用参数中的;如果没有就默认使用当前系统时区。同样的,moment 在 format 的时候,也会考虑当前时区,结合当前时区输出 format 时间,比如:
console.log(moment("2025-02-21T12:08:01.670Z").format("YYYY-MM-DD HH:mm:ss"));
console.log(
moment("2025-02-21T12:08:01.670+08:00").format("YYYY-MM-DD HH:mm:ss")
);
// 如果是当前时区是东八区(Asia/Shanghai),那么输出结果将是:2025-02-21 20:08:01、2025-02-21 12:08:01
// 如果当前时区是零时区(Europe/London),那么输出结果将是:2025-02-21 12:08:01、2025-02-21 04:08:01
所以这个问题就出在 date2 是在服务端 format,而 date1 是在本地 format。由于服务端和本地(东八区)时区不同,所以 format 结果不同。
由此我们也能推测出:如果服务器时间准确的话,服务器时区是零时区。那么怎么验证服务器时间是否准确呢?其实也很简单,只要我们此时获取服务器的 UTC 时间,看看能不能跟我们本地的 UTC 时间一致即可。
其实如果出现了跨时区调用的情况,而第三方只问我们要一个 YYYY-MM-DD HH:mm:ss 这种格式的时间,这其实是不科学的(除非他只数据存储,他的系统中并没有使用到我们的时间数据)。因为 YYYY-MM-DD HH:mm:ss 这种格式的时间,丢失了时区信息,很容易出现时区问题。
总的来说:只要时区不丢失,系统时间准确,大家随便怎么玩,时间都不会出问题。
最终建议
如果出现了跨时区,不要在服务端做 format !!!