乐趣区

关于javascript:考虑时区了吗

前端工程师们,在拿到一个日期 / 工夫数据的时候,你是怎么解决的呢?有没有思考时区的问题呢?

兴许你会说:嗯,没怎么关注时区,不过我测过,没问题。

我能怎么说呢?可能你还没遇到国外的用户吧!

被偷走的一天

如果拿到一个日期字符串 "2021-11-17",为了不便计算和解决数据,可能会先把它封成一个 Date 对象:

const s = "2021-11-17";
const d = new Date(s);

当初应用 d.getDate() 看会失去什么?—— 是 17,没故障!

其实 —— 怎么说呢 —— 这是因为你在中国。如果这段程序在浏览器运行,而浏览器正好位于加拿大,还会是 17 吗?无妨试试。

在 Windows 下进入工夫设置,把工夫改为“太平洋工夫(美国和加拿大)”或者其余 UTC-XX:00 的时区。再到浏览器里去看看,会发现 d.getDate() 失去了 16

如果是在 Linux 下,也能够从新设置零碎工夫,而后用 Node 控制台来查看,如下图:

为什么少了一天?

浏览器始终应用本地工夫

要问为什么少一天,无妨看看 d 到底是个什么样的数据(记得先把时区改回来哦!)

d.toString();         // Wed Nov 17 2021 08:00:00 GMT+0800 ( 中国规范工夫)
d.toLocaleString();   // 2021/11/17 上午 8:00:00

你看,new Date("2021-11-17") 创立日期对象的时候,是把 2021-11-17 当作 UTC 00:00:00 来创立的对象。而这个工夫在中国,就是 2021-11-17 08:00:00。同理,如果是在 -06:00 的加拿大中部,它会是 2021-11-16 18:00:00,这时候 .getDate(),当然会失去 16

在解决工夫这个问题上,浏览器解决得简略粗犷,就是依据零碎的时区设置来依照本地工夫进行解决。如果咱们给的是一个准确的工夫,浏览器这样解决没故障。毕竟工夫是世界的,在这个工夫点上,中国是晚上,加拿大是傍晚 —— 问题在于,Date 对象不能只形容日期,还要形容工夫,它主动按 UTC 补了工夫数据之后,咱们就可能失去非预期的后果。

带工夫局部的字符串

那么,如果拿到的示意工夫的字符串带工夫的呢?是不是会精确一点 …… 试试吧:

// 为了不便查看,正文里的后果数据采纳了简洁的形容
new Date("2021-11-17");           // 2021-11-17 08:00 +08:00
new Date("2021-11-17 00:00:00");  // 2021-11-17 00:00 +08:00
new Date("2021-11-17 09:00:00");  // 2021-11-17 09:00 +08:000

留神第 2 条。在咱们的常识中,2021-11-17 00:00:002021-11-17 应该是同一个工夫吧?后者不带工夫局部,所以默认它是一天的开始,也就是 00:00:00 —— 问题就在这里,Date 对象默认它是 UTC 的 0 时,而不是本地工夫的 0 时。但如果给了工夫,Date 会把它按本地工夫来解决 —— 莫名其妙地就呈现了时差。

该如何做到无差别

因为 new Date() 的时候是按本地工夫来解析的,所以在不同地区的浏览器里,同一个示意日期工夫的字符串会被解析成不同的工夫 —— 你看,中国的晚上 9 点和加拿大的晚上 9 点相对不是同一个工夫对吧!

如果咱们心愿 new Date() 拿到的是同一个工夫怎么办?

说起来也挺简略的,只有拿到字符串蕴含时区信息就行了,比方合乎 RFC3339 规范的工夫示意 2021-11-17T09:00:00+08:00。RFC3339 这里就不详述了,只须要简略地了解为

  • 日期局部是 yyyy-MM-dd 格局
  • 工夫局部是 HH:mm:ssHH:mm:ss.sss 格局
  • 日期和工夫之间应用 T 连贯
  • 时区局部是 Z 或者 ±HH:mm 格局,其中 Z 示意无时差,即 UTC 工夫。比方上面两个工夫是在同一个工夫点上

    2021-11-17T09:00:00+08:00

    2021-11-17T01:00:00Z

很侥幸 Date 能辨认合乎 RFC3339 规范的工夫形容,所以用上例两个字符串创立的工夫是雷同的

const d1 = new Date("2021-11-17T09:00:00+08:00");
const d2 = new Date("2021-11-17T01:00:00Z");
console.log(d1.getTime() === d2.getTime());

还要留神什么

情理曾经讲明确了,然而实际操作中还是会呈现一些奇奇怪怪的问题。而呈现这些问题的起因,归根结底还是没在留神到“时区”。比方,我问几个问题,看能不能搞得明确?

  • 拿到简略示意日期 / 工夫的字符串,不合乎 RFC3339 规范,比方后面提到的 "2021-11-17 09:00:00"。请问他是 UTC 工夫还是本地工夫?
  • 如果是本地工夫 —— 它是应用服务器的本地工夫?还是数据库服务器的本地工夫?还是录入工夫的用户所在时区的本地工夫……?

你看,简略的一个时区问题,并不只是前端单方面的问题,它可能是整个零碎设计的时候就因为疏忽了时区概念而造成的问题。

如果在零碎设计的时候就把时区问题思考进去,所有存储和传输都应用雷同时区的工夫形容,事件就会变得好办得多。当然,如果间接应用 UTC 工夫,或者 Unix Time Stamp 之类准确的形容会更现实。

兴许有人感觉,浏览器的确只反对本地工夫,然而咱们能够用 Moment.js 库,能够用 Day.js 库,能够用 date-fns 库 …… 是的,你能够用各种各样的工具,然而还是得搞明确拿到的工夫形容是精确无歧义的。再强调一次,精确的工夫形容有:

  • 带工夫信息的字符串,比方合乎 RFC3339 规范的字符串形容;
  • 基于某个特定工夫的秒级、毫秒级或更轻微级别的工夫偏移值,比方 Unix Time Stamp。

咱们的程序生在中国,但要面向世界 —— 你思考时区了吗?

退出移动版