这篇文章的目的主要是讲解JavaScript
中的Date
对象,同时解答下面几个困惑我很久的问题:
- 可以解析的表示时间的字符串格式有哪些?
- 时区
TimeZone
- 进位问题
名词约定
表示时间的词很多,比如时间
,日期
,时刻
等,为了便于下面的讲解以及概念的统一,对于下面这种格式
YYYY-MM-DDTHH:mm:ss.sssZ
现在统一约定:
-
YYYY-MM-DD
: 也就是年月日部分使用日期这个词; -
HH:mm:ss.sss
: 也就是时分秒部分使用时刻这个词; -
YYYY-MM-DDTHH:mm:ss.sssZ
: 也就是说并不特指日期和时刻时,我们用时间这个词;
其它名词约定:
-
时间对象
:通过Date
构造函数创建的对象; -
内部插槽
:指官网文档中的internal slot
; -
计算机时区
:指计算机设置的时区,而不是计算机所在地区的时区;
虽然JavaScript
用了Date
这个单词,这个单词翻译过来是日期
,但是从上面的约定来看,翻译成时间
这个词或许更为合适。
至于为什么采用上面这种字符串格式,而不是YYYY/MM/DD HH:mm:ss
或者其它格式进行说明的原因下面会提及。
另外,下面所有代码的运行环境为Chrome浏览器,并且注释部分不一定代表返回值。
内容结构
文章将会从以下几个方面进行讲解:
-
Date
概述 -
Date
作为构造器的用法; -
Date
作为函数的用法; -
Date
上的静态方法; -
getter
相关函数; -
setter
相关函数;
Date概述
通过把Date
作为构造函数使用,可以创建一个时间对象,这个时间对象表示时间长河中的一个时间点。时间对象内部有一个名为[[DateValue]]
的内部插槽,这个内部插槽里面存储的是一个时间戳
。这个时间戳表示的是以世界协调时的1970年1月1日0时0分0秒0毫秒作为起始的毫秒数。
问:不知道是否有人有过疑问,时间戳和时区相关吗,同一时间不同时区获取的时间戳一样吗?
答:根据上面时间戳的规定,时间戳就是从世界协调时开始算的,所以可以说时间戳和时区有关,这个时区就是世界协调时。但也是因为这个时区是定死的,所以不同的时区在同一时间获取到的时间戳都是一样的。
Date作为构造器的用法
在JavaScript
中,我们经常会通过参数的类型和个数让函数作出不同的处理,如:
function justForExample () { // 这个例子只是起说明作用,并无实际意义
let length = arguments.length
if (length === 0) return 'zero parameter'
if (length === 1) return arguments[0]
if (length === 2) return arguments[0] + arguments[1] // 两个参数做加法
if (length === 3) return arguments[0] * arguments[1] * arguments[2] // 三个参数做乘法
// ...
}
Date
也是通过判断参数的类型和个数来进行不同处理的。Date
可以处理的参数的个数是0
到7
个。
当参数的个数是0
的时候,返回一个时间对象,[[DateValue]]
内部插槽的值代表以世界协调时为基准的当前时间的时间戳。
当参数的个数是1
的时候,还需根据参数的类型做进一步判断:
- 如果参数的类型是数字,返回一个时间对象,
[[DateValue]]
内部插槽的值就是这个数字,当然如果这个数据不合法或者超出边界的话,这个插槽的值就不一定是这个数字了。未免本文过于啰嗦,本文不讨论数字不合法或者超出边界等其它情况,有兴趣的可以参阅规范文档。 - 如果参数的类型是字符串,就会调用
Date
上的静态方法parse
,然后把返回值作为[[DateValue]]
的值。具体详见下面的Date.parse
。
当参数的个数大于2
的时候,就会使用下面这种形式:
// 从左到右参数分别代表年,月,日,时,分,秒,毫秒
Date (year, month [,date [, hours [, minutes [, seconds [, ms ]]]]])
上面这种形式接受2
到7
个参数。日的默认值是1
,时,分,秒,毫秒的默认值是0
。
需要注意的是:
- 月份是从
0
开始算的,也就是0
代表1月
,1
代表2月
...11
代表12月
。 -
我在文章的开始说过一个
进位
的问题。我们知道当两个十进制的数相加时,如果低位超出该位的最大值,那么就会向高位进位,以使该位的值在合法范围之内。同样,在Date
构造器以上述形式调用的时候,就会发生自动进位,也就是如果该位的值已经超出了该位的最大值,并不会报错,而是自动执行进位操作:// 进位 new Date(2018, 8, 34) // Thu Oct 04 2018 00:00:00 GMT+0800 (中国标准时间) // 退位,类似于数学中的减法,也是可以的 new Date(2018, 8, -1) // Thu Aug 30 2018 00:00:00 GMT+0800 (中国标准时间)
可以看到,第三个参数
34
表示月份中的天数,但是9
月只有30
天,所以34
并不是9
月合法的日期,所以就产生了进位,月份进入10
月,然后日期是4
日,也就是34-30
。同理,当第三个参数是-1
的时候,会退到8
月份。 -
时区是
计算机时区
。也就是年,月,日,时,分,秒,毫秒代表的是计算机时区的时间。因此,不同的计算机返回的时间可能是不一样的:let date1 = new Date(2018, 8, 8, 8, 8, 8) // Sat Sep 08 2018 08:08:08 GMT+0800 (中国标准时间) date1.getTime() // 1536365288000 // 然后,改了下计算机时区 let date2 = new Date(2018, 8, 8, 8, 8, 8) // Sat Sep 08 2018 08:08:08 GMT+0300 (莫斯科标准时间) date2.getTime() // 1536383288000 (date1 - date2) / 3600 / 1000 // 5,因为date1是东八区,date2是东三区,正好相差5个小时
Date作为函数的用法
当Date
作为普通函数调用时,并不会对参数进行处理,直接返回代表当前时间的字符串。
Date() // "Sat Aug 18 2018 17:26:16 GMT+0800 (中国标准时间)"
Date(2018, 9, 9) // "Sat Aug 18 2018 17:26:21 GMT+0800 (中国标准时间)"
Date上的静态方法
Date
上的静态方法有三个:
- Date.now()
- Date.parse(string)
- Date.UTC(year, month [, date [, hours [, minutes [, seconds [, ms ]]]]])
Date.now()
返回函数调用时,以世界协调时为基准的时间戳。
Date.parse(string)
Date.parse
处理第一个参数,返回一个以世界协调时为基准的时间戳。
在上面介绍Date
作为构造函数使用的时候,当参数的个数是1
,并且类型是字符串时,会在内部调用Date.parse
方法。
我们平时见过很多表示时间的格式:
"2018-08-08 08:08:08"
"2018/08/08 08:08:08"
"2018/8/8 8:8:8"
...
那么,JavaScript
支持的格式有哪些?JavaScript
是否支持上述全部格式呢?
根据文档,规范只定义了一种格式:YYYY-MM-DDTHH:mm:ss.sssZ
,其中T
代表时间的开始,Z
代表时区,也就是世界协调时。时区还可以用+
或者-
拼上HH:mm
来表示。这也是我为什么在开头使用这种格式的原因。当字符串的格式不符合上述格式的时候,就交给具体的实现自己看着办了。
需要注意的是:
- 尽量使用规范规定的字符串格式,否则可能会出现不同的浏览器运行结果不一致的问题;
-
另外上述格式不是所有部分都有才算合法,可以省略某些部分,日期部分允许的格式如下:
YYYY YYYY-MM YYYY-MM-DD
时刻部分允许的格式如下:
THH:mm THH:mm:ss THH:mm:ss.sss
可以只使用上面的日期格式,也可以使用上面的任意一种日期格式+上面的任意一种时刻格式。月、日的默认值是
"01"
,时、分、秒的默认值是"00"
,毫秒的默认值是"000"
。时区缺省的时候,日期+时刻的格式代表的是计算机时区。// 只有日期格式 new Date('2018-08-08') // Wed Aug 08 2018 08:00:00 GMT+0800 (中国标准时间) // 日期+时刻格式,时区默认是计算机时区 new Date('2018-08-08 08:08:08') // Wed Aug 08 2018 08:08:08 GMT+0800 (中国标准时间) // 时区为东三区,我的电脑是在东八区,所以输出的时间是08+05,也就是13点 new Date('2018-08-08 08:08:08+03:00') // Wed Aug 08 2018 13:08:08 GMT+0800 (中国标准时间)
上面第一个例子,可以发现当只有日期格式的时候,字符串是按照世界协调时解析的,也就是世界协调时的2018年8月8日,所以东八区就变成了8点了。文档只规定了日期+时刻格式默认时区是计算机时区,只有日期的时候并没有规定用什么时区,所以尽量不要用日期格式。
所以,就个人而言,我觉得应该尽量避免使用字符串格式来实例化一个时间对象。当想创建一个本地时区的时间对象时,可以使用上面Date
作为构造函数接受2
到7
个参数的那种形式去实例化一个时间对象。 -
没有进位问题,相应部分超出合法值之外就会报错:
new Date('2018-08-34') // Invalid Date
Date.UTC(year, month [, date [, hours [, minutes [, seconds [, ms ]]]]])
类似于上面Date
作为构造函数时,使用2
到7
个参数的形式。不同之处在于:
- 依据的时区不同,该方法参考世界协调时,而不是计算机时区;
- 返回值不同,该方法返回以世界协调时为基准的时间戳,而不是一个时间对象。
所以,可以通过下述方式创建参数以计算机时区和世界协调时为基准的时间对象:
// 参数是以计算机时区为基准
new Date(2018, 8, 8) // Sat Sep 08 2018 00:00:00 GMT+0800 (中国标准时间)
// 参数是以世界协调时为基准
new Date(Date.UTC(2018, 8, 8)) // Sat Sep 08 2018 08:00:00 GMT+0800 (中国标准时间)
getter相关函数
获取一个时间对象的年、月、日、时、分、秒、毫秒等都有对应的方法,这里不再赘述,只简述几个注意点:
-
上述方法针对计算机时区和世界协调时都有对应的一系列方法,如获取小时:
// Date.prototype.getHours() 计算机时区 // Date.prototype.getUTCHours() 世界协调时 let date = new Date(2018, 8, 8, 8) //Sat Sep 08 2018 08:00:00 GMT+0800 (中国标准时间) date.getHours() // 8 date.getUTCHours() // 0
-
Date.prototype.getTime()
返回时间对象的内部插槽[[DateValue]]
的值,也就是以世界协调时为基准的时间戳。
setter相关函数
同getter相关函数一样,设置一个时间对象的年、月、日、时、分、秒、毫秒等都有对应的方法,这里同样不再赘述。只简述几个注意点:
-
同getter相关函数一样,上述方法针对计算机时区和世界协调时都有对应的一系列方法,如设置日期:
// Date.prototype.setHours() 计算机时区 // Date.prototype.setUTCHours() 世界协调时 let date = new Date(2018, 8, 8, 8) //Sat Sep 08 2018 08:00:00 GMT+0800 (中国标准时间) date.setHours(9) // Sat Sep 08 2018 09:00:00 GMT+0800 (中国标准时间) date.setUTCHours(9) // Sat Sep 08 2018 17:00:00 GMT+0800 (中国标准时间)
-
设置月、日、时、分、秒、毫秒的时候,同样会有进位:
let date = new Date(2018, 8, 8) // Sat Sep 08 2018 00:00:00 GMT+0800 (中国标准时间) date.setDate(34) // Thu Oct 04 2018 00:00:00 GMT+0800 (中国标准时间)
总结
希望上述内容对大家有所帮助。如果发现本文有什么错误,您可以在评论区留言。