基本概念
我们先来介绍一些可能当年在地理课上学习过的基本概念。
说起来,时间真是一个神奇的东西。以前人们通过观察太阳的位置来决定时间(比如:使用日晷),这就使得不同经纬度的地区时间是不一样的。后来人们进一步规定以子午线为中心,向东西两侧延伸,每 15 度划分一个时区,刚好是 24 个时区。然后因为一天有 24 小时,地球自转一圈是 360 度,360 度 / 24 小时 = 15 度/小时,所以每差一个时区,时间就差一个小时。
最开始的标准时间(子午线中心处的时间)是英国伦敦的皇家格林威治天文台的标准时间(因为它刚好在本初子午线经过的地方),这就是我们常说的 GMT(Greenwich Mean Time)。然后其他各个时区根据标准时间确定自己的时间,往东的时区时间晚(表示为 GMT+hh:mm)、往西的时区时间早(表示为 GMT-hh:mm)。比如,中国标准时间是东八区,我们的时间就总是比 GMT 时间晚 8 小时,他们在凌晨 1 点,我们已经是早晨 9 点了。
但是 GMT 其实是根据地球自转、公转计算的(太阳每天经过英国伦敦皇家格林威治天文台的时间为中午 12 点),不是非常准确,于是后面提出了根据原子钟计算的标准时间 UTC(Coordinated Universal Time)。
一般情况下,GMT 和 UTC 可以互换,但是实际上,GMT 是一个时区,而 UTC 是一个时间标准。
可以在这里看到所有的时区:http://www.timeanddate.com/time/map/
所以,当我们“展示”某个时间时,明确时区就变得非常重要了。不然你只说现在是 2016-01-11 19:30:00
,然后不告诉我时区,我其实是没法准确知道时间的(当然,我可以认为这个时间是我所在时区的当地时间)。如果你说现在是 2016-01-11 19:30:00 GMT+0800
,那我就知道这个时间是东八区的时间了。如果我在东八区,那时间就是 19:30,如果我在 GMT 时区,那时间就是 11:30(减掉 8 小时)。
JavaScript 中的“时间”
我们现在来介绍下 JavaScript 中的“时间”,包括:Date
、Date.parse
、Date.UTC
、Date.now
。
注:下面的代码示例可以在 node shell 里面运行,如果你运行的时候结果和下面的不一致,那可能咱们不在一个时区:)
Date 构造器
构造时间的方法有下面几种:
new Date(); // 当前时间
new Date(value); // 自 1970-01-01 00:00:00 UTC 经过的毫秒数
new Date(dateString); // 时间字符串
new Date(year, month[, day[, hour[, minutes[, seconds[, milliseconds]]]]]);
需要注意的是:构造出的日期用来显示时,会被转换为本地时间(调用 toString
方法):
> new Date()
// 这是 Nodejs 6 之前输出的结果
Mon Jan 11 2016 20:15:18 GMT+0800 (CST)
// Nodejs 6 及其之后版本会输出 UTC 时间
2017-07-30T11:47:28.449Z
打印出我写这篇文章时的本地时间。后面的 GMT+0800
表示是“东八区”,CST
表示是“中国标准时间(China Standard Time)”。
有一个很“诡异”的地方是如果我们直接使用 Date
,而不是 new Date
,得到的将会是字符串,而不是 Date
类型的对象:
> typeof Date()
'string'
> typeof new Date()
'object'
时间字符串
我们先说最复杂的时间字符串形式。它实际上支持两种格式:一种是 RFC-2822 的标准;另一种是 ISO 8601 的标准。我们主要介绍后一种。
ISO 8601
ISO 8601的标准格式是:YYYY-MM-DDTHH:mm:ss.sssZ
,分别表示:
-
YYYY
:年份,0000 ~ 9999 -
MM
:月份,01 ~ 12 -
DD
:日,01 ~ 31 -
T
:分隔日期和时间 -
HH
:小时,00 ~ 24 -
mm
:分钟,00 ~ 59 -
ss
:秒,00 ~ 59 -
.sss
:毫秒 -
Z
:时区,可以是:Z(UFC)、+HH:mm、-HH:mm
这里我们主要来说下 T
、以及 Z
。
T
T
也可以用空格表示,但是这两种表示有点不一样,T
其实表示 UTC
,而空格会被认为是本地时区(前提是不通过 Z
指定时区)。比如下面的例子:
> new Date('1970-01-01 00:00:00')
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)
> new Date('1970-01-01T00:00:00')
Thu Jan 01 1970 08:00:00 GMT+0800 (CST)
示例 1 的空格表示法被当做了本地时区,所以显示的时间和传入的时间一致。
示例 2 的 T
被当做 UTC 时间,所以显示的时间会加上本地时区(东八区)的 8 小时偏移。实际上,1970-01-01T00:00:00
等价于 1970-01-01 00:00:00Z
。
Z
Z
用来表示传入时间的时区(zone),不指定并且没有使用 T
分隔而是使用空格分隔时,就按本地时区处理,比如下面的例子:
> new Date('1970-01-01T00:00:00+08:00')
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)
> new Date('1970-01-01 00:00:00')
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)
> new Date('1970-01-01T00:00:00+00:00')
Thu Jan 01 1970 08:00:00 GMT+0800 (CST)
示例 1 是东八区时间,显示的时间和传入的时间一致(因为我本地时区是东八区)。
示例 2 和示例 1 结果一样,不指定时区就是本地时区。
示例 3 指定时区为 GMT 时区(偏移为 0),显示的时间会加上本地时区的偏移(8 小时)。
RFC-2822
RFC-2822 的标准格式大概是这样:Wed Mar 25 2015 09:56:24 GMT+0100
。其实就是上面显示时间时使用的形式:
> new Date('Thu Jan 01 1970 00:00:00 GMT+0800 (CST)')
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)
除了能表示基本信息,还可以表示星期,但是一点也不容易读,不建议使用。完整的规范可以在这里查看:http://tools.ietf.org/html/rfc2822#page-14
时间戳
Date
构造器还可以接受整数,表示想要构造的时间自 UTC 时间 1970-01-01 00:00:00
经过的毫秒数。比如下面的代码:
> new Date(1000 * 1)
Thu Jan 01 1970 08:00:01 GMT+0800 (CST)
传人 1 秒,等价于:1970-01-01 00:00:01Z
,显示的时间加上了本地时区的偏移(8 小时)。
多参数
最后,Date
构造器还支持传递多个参数,这种方法就没办法指定时区了,都当做本地时间处理。比如下面的代码:
> new Date(1970, 0, 1, 0, 0, 0)
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)
显示时间和传入时间一致,均是本地时间。注意:月份是从 0 开始的。
Date.parse
Date.parse
接受一个时间字符串,如果字符串能正确解析就返回自 UTC 时间 1970-01-01 00:00:00
经过的毫秒数,否则返回 NaN
:
> Date.parse('1970-01-01 00:00:00')
-28800000
> new Date(Date.parse('1970-01-01 00:00:00'))
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)
> Date.parse('1970-01-01T00:00:00')
0
> new Date(Date.parse('1970-01-01T00:00:00'))
Thu Jan 01 1970 08:00:00 GMT+0800 (CST)
示例 1,-28800000 换算后刚好是 8 小时表示的毫秒数,28800000 / (1000 * 60 * 60)
,我们传入的是本地时区时间,等于 UTC 时间的 1969-12-31 16:00:00
,和 UTC 时间 1970-01-01 00:00:00
相差刚好 -8 小时。
示例 2,将 parse 后的毫秒数传递给构造器,最后显示的时间加上了本地时区的偏移(8 小时),所以结果刚好是 1970-01-01 00:00:00
。
示例 3,传入的是 UTC 时区时间,所以结果为 0。
示例 4,将 parse 后的毫秒数传递给构造器,最后显示的时间加上了本地时区的偏移(8 小时),所以结果刚好是 1970-01-01 08:00:00
。
Date.UTC
Date.UTC
接受的参数和 Date
构造器多参数形式一样,然后返回时间自 UTC 时间 1970-01-01 00:00:00
经过的毫秒数:
> Date.UTC(1970,0,1,0,0,0)
0
> Date.parse('1970-01-01T00:00:00')
0
> Date.parse('1970-01-01 00:00:00Z')
0
可以看出,Date.UTC
进行的是一种“绝对运算”,传入的时间就是 UTC 时间,不会转换为当地时间。
Date.now
Date.now
返回当前时间距 UTC 时间 1970-01-01 00:00:00
经过的毫秒数:
> Date.now()
1452520484343
> new Date(Date.now())
Mon Jan 11 2016 21:54:55 GMT+0800 (CST)
> new Date()
Mon Jan 11 2016 21:55:00 GMT+0800 (CST)
原文:https://segmentfault.com/a/1190000004292140
参考资料
一不小心写的有点长了,下面列出参考资料供大家进一步学习:
http://www.timeanddate.com/time/gmt-utc-time.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC
http://tools.ietf.org/html/rfc2822#page-14
http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
http://www.w3schools.com/js/js_date_formats.asp
http://momentjs.com/timezone/docs/
http://sequelize.readthedocs.org/en/latest/api/sequelize/
https://github.com/felixge/node-mysql
《MySQL 技术内幕》