零散专题31 JS中的日期对象
最近做一个重构的业务,使用到了Mongo数据库,在接口中需要返回数据的的更新时间。之前数据库中都使用了时间戳记录时间,返给前端的数据直接使用时间戳进行格式化,并且查询条件也是由时间戳进行比对。
没想到,做数据写入接口的同事,将这个字段由时间戳改为了使用Mongo自带的标准的日期格式:ISODate("2016-01-01T00:00:00Z")
突然发现自己一时间对JS中的日期对象、对时间的各种表示方法并没有一个比较清晰的脉络。所以想用一点时间对这块知识做一个梳理、总结。
1 几种时间标准
首先要明确,时间标准和时刻的关系。
我理解,时刻对于整个地球来说是唯一的。就在我写下这行文字的这一时刻,无论是美国、老挝还是柬埔寨,大家都经历了同一时刻,对于时间轴的刻度,全球是唯一的。
但是在不同的时区的人,代表这一刻的时间是各不相同的,我所在的北京时间是晚上21:26,但是对于美国、老挝、柬埔寨可能时间各不相同。
时区是地球上的区域使用同一个时间定义。以前,人们通过观察太阳的位置(时角)决定时间,这就使得不同经度的地方的时间有所不同(地方时)。1863年,首次使用时区的概念。时区通过设立一个区域的标准时间部分地解决了这个问题。
不同给的时区有对应的时间标准,对于中国的开发者来说,常见的时间标准有GMT/UTC/CST
(1)GMT
GMT,格林尼治标准时间,是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义为通过那里的经线。但是格林尼治时间本身有一些缺陷,并不准确,已经被原子钟报时的协调世界时(UTC)替代。
(2)UTC
刚才提到了UTC,协调世界时,是目前最主要的是世界时间标准,以原子时为基础。它的精确度比GTM更高,但是对于大多数用途,UTC时间被认为能够与GMT时间互换。
(3)CST
CST,是北京时间,也叫做中国标准时间,比UTC时间快八个小时,与澳门、香港、台北、吉隆坡、新加坡等地的标准时间相同。
(4)小结
也就是说,UTC时间是GMT的时间的升级版,基本可以认为是相同的,有如下的关系:
1 | GTM ≈ UTC = CST + 8 |
2 ISO时间
ISO时间与前面提到的时间标准属于不同的概念,它并不是一种时间标准,二是一种时间的表示方法。
ISO时间的全称是ISO 8601,是国际标准化组织的日期和时间的表示方法。目前是2004年12月1日发行的第三版标ISO8601:2004
标准中规定了日期和时间的组合表表示法,只使用数字为基本格式,使用冒号间隔开小时、分、秒,小时、分和秒都用2位数表述。日期和时间之间要一个大写字母T
。
如果时间与UTC时间相同,那么(不加空格地)在时间最后加一个大写字母Z
,比如下午2点30分5秒表示为14:30:05Z
,其他时区用实际时间加上时差表示,比如当地的UTC+8时间表示为22:30:05+08:00
所以:
1 | 2004-05-03T17:30:08+08:00 |
前面提到的,Mongo里的时间ISODate("2016-01-01T00:00:00Z")
使用的就是ISO表示法的UTC时间。
3 时间戳
时间戳也是常见的表达时间的方式,Unix时间戳指的是格林威治时间1970-01-01 00:00:00
(北京时间1970-01-01 08:00:00
)其到现在(或某个指定时间)的总秒数。它用来唯一的标识某一刻的时间。
那么,北京时间的时间戳和UTC的时间戳是一样的吗?
当然是一样的,前面提到了,时间戳是用来唯一的标识某一刻的时间,它是独一无二的。虽然北京时间和UTC时间相差了8个小时,但是正如上面提到的,北京时间和UTC时间计算时间戳的起点也相差了8个小时。
所以时间戳是没有时区的区别的。
4 JavaScript中的日期对象
ECMAScript中的Date
类型使用UTC时间的1970年1月1日午夜(零时)开始经过的毫秒数(注意是毫秒)来保存日期。
创建一个日期对象:
1 | const now = new Date() |
在调用Date
构造函数而不传递参数的情况下,新创建的对象自动获得当前日期和时间。如果想根据特定的日期和时间创建日期对象,必须传入表示该日期的毫秒时间戳。
构造函数也可以接受一个字符串返回相应的日期对象,这是因为构造函数在后台调用了Date.parse()
方法,将字符串转换为了对应日期的毫秒数。
Date
可以接受多种日期格式,常用的有以下几种:
1 | // 时间戳 |
在使用Date
构造函数创建一个日期对象时,日期和时间都是基于本地时区而非GMT来创建的:
1 | new Date() |
上面的代码创造的日期对象是基于本地时区,也就是CST时区(为GMT+8)。当然JavaScript也提供了各种方法来进行各种事件标准间的转换:
1 | const now = new Date() |
Date
对象的各种API可以查看MDN的文档,要注意的是日期对象的valueOf
方法是取时间戳的值,效果与getTime
是相同的。日期对象转换为数字,结果就是其时间戳。
1 | now.valueOf() === now.getTime() |
日期对象进行比较时,如果是两个对象进行比较,结果永远是不等的,原因和你直接比较两个{}
不等是相同的,它们指向的不同的内存地址。但是可以将它们转换为字符串或者数字(时间戳)进行比较,而且在进行大于或者小于的比较时,是不用进行转换可以直接进行比较的(我猜测执行了隐式的转换,先转换为了数字,实际进行比较的是时间戳的值)
1 | var startDate1 = new Date("02/10/2012"); |
对于日期对象,比较简单的转换和使用完全可以使用原生API实现,稍微复杂的一些可以借助Moment.js和date-fns,后者是纯函数,是不变的,而且更容易做到按需引入,所以更加推荐。
参考
- 时区@维基百科
- 格林尼治平时@维基百科
- 协调世界时@维基百科
- 北京时间@维基百科
- ISO 8601@维基百科
- 时间戳@百度百科
- JavaScript高级程序设计(第三版)
- Date@MDN