理解时间和时区

假设小明在北京(+8)执行了一行代码,new Date('2022-01-01 00:00:00:000').valueOf(),同时小红在ID(+7)地区也执行了这行代码,请问他们获得的时间戳是否相同?

答: 不相同。为什么呢,因为北京的 22年元旦0点的时候,ID比我们早了一个钟, 时钟上显示着 '2021-12-31 23:00:00',而当ID到了22年元旦0点的时候,我们已经过了一个钟时钟上显示的是 '2022-01-01 01:00:00'

// 北京 +8
new Date('2022-01-01 00:00:00:000').valueOf()  // 1640966400000
// ID +7
new Date('2022-01-01 00:00:00:000').valueOf()  // 1640970000000

假设小明在北京(+8)执行了一行代码,new Date(1640966400000),同时小红在ID(+7)地区也执行了这行代码,请问他们获得的时间戳是否相同?

答: 不相同。这个原因和上面是一样的,同一个时刻,不同的地区上,时钟显示的是不一样的,ID比我们早了一个时区,自然是比我们快了一个钟。

// 北京 +8
new Date(1640966400000)  // Sat Jan 01 2022 00:00:00 GMT+0800 (中国标准时间)
// ID +7
new Date(1640966400000)  // Fri Dec 31 2021 23:00:00 GMT+0700 (印度尼西亚西部时间)

从这两个例子我们可以看到,

  • 时间字符串相同时,在不同的地区获得的时间戳是不同的
  • 时间戳相同时,在不同的地区获取的时间字符串是不同的
  • 看到这里,你似乎已经对时间、时区、时间字符串有了一个初步的印象和理解。

    关于时间和时区

    时间是什么?

    这是个很深刻的问题了,关于时间的定义,很复杂。但是简单来说,指的是某一瞬间。

    时间戳、UTC、GMT这些都是什么?

    看完这篇文章 日期与时间(UTC、GMT、时间戳、时区) 相信你会得到答案。

    但归根在本质上,它们就是时间,是时间在计算机上的记录。

    时间戳需要理解时区概念吗?

    时间戳是不需要考虑时区概念的。

    为什么这样说,我们假设 1657535893504(东八区 2022-07-11 18:39:22) 这个时间戳,无论在哪个国家哪个地区,它都代表着某一个时刻时间,它是时间本身的记录。

    那时间字符串有时区概念呢?

    当然有,因为在不同的时区下,同一个时间字符串代表的是不同的时间本身。 时间字符串只是当地时间的一个表达。

    时间字符串和 UTC 、 GMT有什么区别,不都是字符串吗?

    很好,盲点你已经发现了华生。 '2022-01-01 00:00:00' 和 'Sat Jan 01 2022 00:00:00 GMT+0800 (中国标准时间)' 虽然同样都是字符串,但是前者没有时区,所以在不同的时区下,它代表着不同的时间,后者带上了时区,它永久地指向了一个特定的时间。

    如何理解dayjs对象?

    我们应该知道dayjs对象本质上就是时间,在理解 时区、本地字符串、时间的关系和转换时,我们需要把dayjs对象当成时间当成时间戳去处理。

    总结: 我们可以看到,时间字符串需要考虑时区,是因为它本身不携带时区信息,它在不同的时区时指向的是不同的时间;UTC、时间戳、GMT本身就代表着时间,所以说在处理它们的时候,我们不需要考虑时区的问题。

    Dayjs的使用

    从上面我们知道, 时间字符串 + 时区 = 时间。 那我们再来了解一下dayjs的api

    dayjs()

    这是最基础的一个api。调用时,时区都是机器时区,无论你是否设置了tz的默认时区。

    从下面可以看到,当我们传入一个普通的时间字符串时,它是指我们本地的2022-01-01

    dayjs().tz() // tz(timezone?: string, keepLocalTime?: boolean): Dayjs

    interface Dayjs {
        tz(timezone?: string, keepLocalTime?: boolean): Dayjs
    

    可以注意到,此时的tz是dayjs实例上的共有函数,它的作用是转换时区。记住我们上面的等式, 时间字符串 + 时区 = 时间。所以,时区改变了,要么时间字符串改变,要么时间改变,这里是通过第二个参数去控制的,不传第二个函数默认是false

  • keepLocalTimefalse, 时间不变,时间字符串改变了
  • keepLocalTimetrue , 时间字符串没变,时间变了
    interface DayjsTimezone {
        (date: ConfigType, timezone?: string): Dayjs
        (date: ConfigType, format: string, timezone?: string): Dayjs
    const tz: DayjsTimezone
    

    首先和第二个函数不同,它是挂载在dayjs上的静态函数,它的作用和第一个函数 dayjs() 类似,都是用来构造dayjs对象的,但是不同的是,你可以指定你的目标时区,如果第二个参数没传,会使用tz.setDefault指定的时区

    仍然是那个等式, 时间字符串 + 时区 = 时间,所以如果第一个参数传入了时间字符串,那么就是 时间字符串 + 指定时区, 如果传入的是时间,则format的时候是结果会不同

    传入时间字符串

    传入dayjs

    在上面我们说过,dayjs对象需要当成时间来理解,所以传入dayjs 等同于 传入时间。那么当我们需要保持时间字符串不变时,那我们就需要先format一下。

    可能有的同学已经发现了,既然传入的是dayjs,为啥不直接调用第二个例子, tz这个实例方法呢? 这里有两个理由:

    如果是时间选择器出来的时间,这个dayjs可能是没有tz这个实例方法的,因为没有继承过timezone

    dayjs.extend(utc)
    dayjs.extend(timezone)
    

    通常来说,都是用dayjs.tz.setDefault 设置一个默认的目标时区,而tz这个实例方法,又不能绕开第一个参数去设置第二个参数

    Dayjs VS Moment

    Dayjs和moment都支持处理时区,且API相似,但他们有着一个区别,

    那就是 moment在设置时区后,不再保有原有时区,而dayjs仅影响.tz的对象

    关于这个区别,可以看这个issue github.com/iamkun/dayj…

    具体的业务场景

    时间选择器组件onValueChange出来的的时间要怎么理解?

    首先我们可以理解,时间选择器组件上假设选择了 2022-01-01, 那它其实背后是我们机器时区下的 2022-01-01。所以第一个,我们是需要将它转换至目标时区,其实是我们的场景A, 转换的过程中是需要改变时间戳。

    时间选择器组件的disabled出来的时间要怎么理解?

    参考上面的例子,我们知道时间选择器出来的都是机器时区下需要做一次转换。 因为在实际的业务过程中,例如说禁用今天,这个所谓的今天,是我们目标时区下的今天,而不是我们机器时区下的今天,所以同理需要做一次转换。

    后端传回来的时间要怎么理解?

    通常来说,后端传回来的都会是时间戳、 UTC、GMT等时间的一种表达方式,在上面我们说了,这些都是和时区无关的,就是时间本质。 要怎么理解这个时间本质呢,通俗的讲,你需要的是ID 2022-01-01 零点,它是一个准确的时间,那么后端返回的时间就精准地指向了这一刻。

    dayjs.tz(timestamp)
    

    要传给后端的时间要怎么理解

    参考上面的例子,要传给后端的也会是一个准确的时间。

    date.format('YYYY-MM-DD')
    // or
    date.valueOf()
    

    单纯的时间处理函数怎么理解和处理呢?

    对于这种函数,我们应该直接限制它的入参为准确的时间,鉴于ISO、GMT等均为字符串,可以直接限制为时间戳,或者是Dayjs类型,尽量避免参数可以为string,这会很容易造成混淆。

    type processTime = (d: Dayjs | number, region: Region) => void
    

    如果说参数确实需要有一个是字符串,我们需要一个额外的参数,去标识这个时间的时区,通常来说,是标识它到底是机器时区还是目标时区的时间

    type processTime = (d: string, region: Region, isLocal: boolean) => void
    if (isLocal) {
      const date = dayjs(d).tz();
    } else {
      const date = dayjs.tz(d);
      
  • 想自学JS吗?想提升JS底层原理吗?76张脑图带你彻底搞懂原生JS
  • 32个手写JS,巩固你的JS基础(面试高频)
  • 2021 前端面试 | “HTML + CSS + JS”专题
  • 私信