转载自:http://web.jobbole.com/89120/
本部分的知识图谱请参考编程语言知识图谱-时间与日期。
本文JavaScript 时间与日期处理实战:你肯定被坑过从属于笔者的Web 前端入门与最佳实践中 JavaScript 入门与最佳实践系列文章。
GMT即「格林威治标准时间」(Greenwich Mean Time,简称G.M.T.),指位于英国伦敦郊区的皇家格林威治天文台的标准时间,因为本初子午线被定义为通过那里的经线。然而由于地球的不规则自转,导致GMT时间有误差,因此目前已不被当作标准时间使用。UTC是最主要的世界时间标准,是经过平均太阳时(以格林威治时间GMT为准)、地轴运动修正后的新时标以及以「秒」为单位的国际原子时所综合精算而成的时间。UTC比GMT来得更加精准。其误差值必须保持在0.9秒以内,若大于0.9秒则由位于巴黎的国际地球自转事务中央局发布闰秒,使UTC与地球自转周期一致。不过日常使用中,GMT与UTC的功能与精确度是没有差别的。协调世界时区会使用“Z”来表示。而在航空上,所有使用的时间划一规定是协调世界时。而且Z在无线电中应读作“Zulu”(可参见北约音标字母),协调世界时也会被称为“Zulu time”。
人们经常会把时区与UTC偏移量搞混,UTC偏移量代表了某个具体的时间值与UTC时间之间的差异,通常用HH:mm形式表述。而TimeZone则表示某个地理区域,某个TimeZone中往往会包含多个偏移量,而多个时区可能在一年的某些时间有相同的偏移量。譬如America/Chicago, America/Denver, 以及 America/Belize在一年中不同的时间都会包含 -06:00 这个偏移。
Unix时间戳表示当前时间到1970年1月1日00:00:00 UTC对应的秒数。注意,JavaScript内的时间戳指的是当前时间到1970年1月1日00:00:00 UTC对应的毫秒数,和unix时间戳不是一个概念,后者表示秒数,差了1000倍。
1
2
|
YYYY
/
MM
/
DD
HH
:
MM
:
SS
±
timezone
(时区用
4位数字表示
)
// eg 1992/02/12 12:23:22+0800
|
国际标准化组织的国际标准ISO 8601是日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前最新为第三版ISO8601:2004,第一版为ISO8601:1988,第二版为ISO8601:2000。年由4位数组成,以公历公元1年为0001年,以公元前1年为0000年,公元前2年为-0001年,其他以此类推。应用其他纪年法要换算成公历,但如果发送和接受信息的双方有共同一致同意的其他纪年法,可以自行应用。
1
2
3
4
5
6
|
YYYY
-
MM
-
DDThh
:
mm
:
ss
±
timezone
(时区用
HH
:
MM表示
)
1997
-
07
-
16T08
:
20
:
30Z
// “Z”表示UTC标准时区,即"00:00",所以这里表示零时区的`1997年7月16日08时20分30秒`
//转换成位于东八区的北京时间则为`1997年7月17日16时20分30秒`
1997
-
07
-
16T19
:
20
:
30
+
01
:
00
// 表示东一区的1997年7月16日19时20秒30分,转换成UTC标准时间的话是1997-07-16T18:20:30Z
|
JavaScript为我们提供了不是很好用的Date对象作为时间日期对象,Date()直接返回当前时间字符串,不管参数是number还是任何string。而new Date()则是会根据参数来返回对应的值,无参数的时候,返回当前时间的字符串形式;有参数的时候返回参数所对应时间的字符串。new Date()对参数不管是格式还是内容都要求,且只返回字符串,标准的构造Date对象的方法有:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 不带new操作符,像一个函数一样调用。它将忽略所有传入的参数,并返回当前日期和时间的一个字符串表示。
new
Date
(
)
;
// 可接受一个数字参数,该参数表示设定时间与1970年1月1日0点之间的毫秒数。
new
Date
(
value
)
;
// 可接受一个字符串参数,参数形式类似于Date.parse()方法。但parse()方法返回的是一个数字,而Date()函数返回的是一个对象。
new
Date
(
dateString
)
;
// 可接受参数形式类似于Date.UTC()方法的参数,但Date.UTC()方法返回是一个毫秒数,且是UTC时间,而Date()函数返回是一个对象,且是本地时间。
new
Date
(
year
,
month
[
,
day
[
,
hour
[
,
minutes
[
,
seconds
[
,
milliseconds
]
]
]
]
]
)
;
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
-
year:四位年份,如果写成两位数,则加上
1900
month:表示月份,
0表示一月,
11表示
12月
date:表示日期,
1到
31
hour:表示小时,
0到
23
minute:表示分钟,
0到
59
second:表示秒钟,
0到
59
ms:表示毫秒,
0到
999
|
这里需要注意的是,月份month参数,其计数方式从0开始,而天day参数,其计数方式从1开始。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
new
Date
(
)
;
//Fri Aug 21 2015 15:51:55 GMT+0800 (中国标准时间)
new
Date
(
1293879600000
)
;
new
Date
(
'2011-01-01T11:00:00'
)
new
Date
(
'2011/01/01 11:00:00'
)
new
Date
(
2011
,
0
,
1
,
11
,
0
,
0
)
new
Date
(
'jan 01 2011,11 11:00:00'
)
new
Date
(
'Sat Jan 01 2011 11:00:00'
)
//Sat Jan 01 2011 11:00:00 GMT+0800 (中国标准时间)
new
Date
(
'sss'
)
;
new
Date
(
'2011/01/01T11:00:00'
)
;
new
Date
(
'2011-01-01-11:00:00'
)
new
Date
(
'1293879600000'
)
;
//Invalid Date
new
Date
(
'2011-01-01T11:00:00'
)
-
new
Date
(
'1992/02/11 12:00:12'
)
//596069988000
|
如果需要从当前的时间对象获取其相应的时间戳,我们可以使用getTime或者valueOf(),返回距离1970年1月1日0点的毫秒数:
1
2
3
4
5
6
7
8
|
var
date1
=
new
Date
(
2007
,
0
,
1
)
;
var
date2
=
new
Date
(
2007
,
1
,
1
)
;
console
.
log
(
date1
>
date2
)
;
//false
console
.
log
(
date1
<
date2
)
;
//true
// ECMAScript5新增了now()方法,该方法返回当前时间距离1970年1月1日0点UTC的毫秒数。该方法不支持传递参数
Date
.
now
=
function
(
)
{
return
(
new
Date
(
)
)
.
getTime
(
)
}
|
另外Date对象还有一个静态方法同样返回给定日期的毫秒数。但其参数并不是一个字符串,而是分别代表年、月、日、时、分、秒、毫秒的数字参数:
1
2
3
4
5
6
|
console
.
log
(
Date
.
UTC
(
1970
)
)
;
//NaN
console
.
log
(
Date
.
UTC
(
1970
,
0
)
)
;
//0
console
.
log
(
Date
.
UTC
(
1970
,
0
,
2
)
)
;
//86400000
console
.
log
(
Date
.
UTC
(
1970
,
0
,
1
,
1
)
)
;
//3600000
console
.
log
(
Date
.
UTC
(
1970
,
0
,
1
,
1
,
59
)
)
;
//714000
console
.
log
(
Date
.
UTC
(
1970
,
0
,
1
,
1
,
59
,
30
)
)
;
//717000
|
还是需要强调下,JavaScript内的时间戳指的是当前时间到1970年1月1日00:00:00 UTC对应的毫秒数,和unix时间戳不是一个概念,后者表示秒数,差了1000倍。new Date(timestamp)
中的时间戳必须是number
格式,string
会返回Invalid Date
。所以比如new Date('11111111')
这种写法是错的。
JavaScript原生Date对于时间字符串的解析真的是槽点满满,假设我们希望以DD/MM/YYYY的格式进行解析,那么它是无法识别的:
1
2
|
var
a
=
new
Date
(
'01/12/2016'
)
;
//December 1 2016 in DD/MM/YYYY format
//"Tue Jan 12 2016 00:00:00 GMT-0600 (Central Standard Time)"
|
另外,在ES5的标准中,其对ISO 8601标准的字符串进行了一个神奇的断言:所有没有提供时区的字符串默认为标准时区。换言之,你会发现你解析出来的时间和你预期中的不一样,而且它打印的时候是按照本地时区又进行了转换:
1
2
3
4
5
6
7
|
//US local format
var
a
=
new
Date
(
'1/1/2016'
)
;
//"Fri Jan 01 2016 00:00:00 GMT-0600 (Central Standard Time)"
//ISO 8601
var
a
=
new
Date
(
'2016-01-01'
)
;
//"Thu Dec 31 2015 18:00:00 GMT-0600 (Central Standard Time)"
|
ES 2015标准中则是修复了该Bug,不过还是会让人觉得头大,毕竟你不知道你代码的最终运行环境会是ES5还是ES6。Date对象也有一个parse方法,用于解析一个日期字符串,参数是一个包含待解析的日期和时间的字符串,返回从1970年1月1日0点到给定日期的毫秒数。该方法会根据日期时间字符串格式规则来解析字符串的格式,除了标准格式外,以下格式也支持。如果字符串无法识别,将返回NaN。
1
2
3
4
5
6
7
|
console
.
log
(
Date
.
parse
(
'6/13/2004'
)
)
;
//1087056000000
console
.
log
(
Date
.
parse
(
'January 12,2004'
)
)
;
//1073836800000
console
.
log
(
Date
.
parse
(
'Tue May 25 2004 00:00:00 GMT-0700'
)
)
;
//1085468400000
console
.
log
(
Date
.
parse
(
'2004-05-25T00:00:00'
)
)
;
//1085443200000
console
.
log
(
Date
.
parse
(
'2016'
)
)
;
//1451606400000
console
.
log
(
Date
.
parse
(
'T00:00:00'
)
)
;
//NaN
console
.
log
(
Date
.
parse
(
)
)
;
//NaN
|
在ECMAScript5中,如果使用标准的日期时间字符串格式规则的字符串中,数学前有前置0,则会解析为UTC时间,时间没有前置0,则会解析为本地时间。其他情况一般都会解析为本地时间
1
2
3
|
console
.
log
(
Date
.
parse
(
'7/12/2016'
)
)
;
//1468252800000
console
.
log
(
Date
.
parse
(
'2016-7-12'
)
)
;
//1468252800000
console
.
log
(
Date
.
parse
(
'2016-07-12'
)
)
;
//1468281600000
|
Date对象提供了一系列get*方法,用来获取实例对象某个方面的值。具体的Get函数列表详见附录:
1
2
3
4
5
6
7
|
var
d
=
new
Date
(
'January 6, 2013'
)
;
d
.
getDate
(
)
// 6
d
.
getMonth
(
)
// 0
d
.
getYear
(
)
// 113
d
.
getFullYear
(
)
// 2013
d
.
getTimezoneOffset
(
)
// -480
|
同样的,Date对象还提供了一系列的Set方法:
1
2
3
4
5
6
7
8
9
|
var
d1
=
new
Date
(
'January 6, 2013'
)
;
d1
.
setDate
(
32
)
// 1359648000000
d1
// Fri Feb 01 2013 00:00:00 GMT+0800 (CST)
var
d2
=
new
Date
(
'January 6, 2013'
)
;
d
.
setDate
(
-
1
)
// 1356796800000
d
// Sun Dec 30 2012 00:00:00 GMT+0800 (CST)
|
我们可以巧用Set方法的特性,set*方法的参数都会自动折算。以setDate为例,如果参数超过当月的最大天数,则向下一个月顺延,如果参数是负数,表示从上个月的最后一天开始减去的天数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
var
d1
=
new
Date
(
'January 6, 2013'
)
;
d1
.
setDate
(
32
)
// 1359648000000
d1
// Fri Feb 01 2013 00:00:00 GMT+0800 (CST)
var
d2
=
new
Date
(
'January 6, 2013'
)
;
d
.
setDate
(
-
1
)
// 1356796800000
d
// Sun Dec 30 2012 00:00:00 GMT+0800 (CST)
var
d
=
new
Date
(
)
;
// 将日期向后推1000天
d
.
setDate
(
d
.
getDate
(
)
+
1000
)
;
// 将时间设为6小时后
d
.
setHours
(
d
.
getHours
(
)
+
6
)
;
// 将年份设为去年
d
.
setFullYear
(
d
.
getFullYear
(
)
-
1
)
;
|
类型转换时,Date对象的实例如果转为数值,则等于对应的毫秒数;如果转为字符串,则等于对应的日期字符串。所以,两个日期对象进行减法运算,返回的就是它们间隔的毫秒数;进行加法运算,返回的就是连接后的两个字符串。
1
2
3
4
5
6
7
8
|
var
d1
=
new
Date
(
2000
,
2
,
1
)
;
var
d2
=
new
Date
(
2000
,
3
,
1
)
;
d2
-
d1
// 2678400000
d2
+
d1
// "Sat Apr 01 2000 00:00:00 GMT+0800 (CST)Wed Mar 01 2000 00:00:00 GMT+0800 (CST)"
|
Date对象提供了一系列的to*
方法来支持从Date对象转化为字符串,具体的函数列表详见附录:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
var
d
=
new
Date
(
2013
,
0
,
1
)
;
d
.
toString
(
)
// "Tue Jan 01 2013 00:00:00 GMT+0800 (CST)"
d
.
toUTCString
(
)
// "Mon, 31 Dec 2012 16:00:00 GMT"
d
.
toISOString
(
)
// "2012-12-31T16:00:00.000Z"
d
.
toJSON
(
)
// "2012-12-31T16:00:00.000Z"
d
.
toDateString
(
)
// "Tue Jan 01 2013"
d
.
toTimeString
(
)
// "00:00:00 GMT+0800 (CST)"
d
.
toLocaleDateString
(
)
// 中文版浏览器为"2013年1月1日"
// 英文版浏览器为"1/1/2013"
d
.
toLocaleTimeString
(
)
// 中文版浏览器为"上午12:00:00"
// 英文版浏览器为"12:00:00 AM"
|
1
2
3
4
5
|
const
nMS
=
1320
;
//以毫秒单位表示的差值时间
var
nD
=
Math
.
floor
(
nMS
/
(
1000
*
60
*
60
*
24
)
)
;
var
nH
=
Math
.
floor
(
nMS
/
(
1000
*
60
*
60
)
)
%
24
;
var
nM
=
Math
.
floor
(
nMS
/
(
1000
*
60
)
)
%
60
;
var
nS
=
Math
.
floor
(
nMS
/
1000
)
%
60
;
|
浏览器获取当前用户所在的时区等信息只和系统的日期和时间设置里的时区以及时间有关。区域和语言设置影响的是浏览器默认时间函数(Date.prototype.toLocaleString等)显示的格式,不会对时区等有影响。Date有个Date.prototype.toLocaleString()方法可以将时间字符串返回用户本地字符串格式,这个方法还有两个子方法Date.prototype.toLocaleDateString和Date.prototype.toLocaleTimeString,这两个方法返回值分别表示日期和时间,加一起就是Date.prototype.toLocaleString的结果。这个方法的默认参数会对时间字符串做一次转换,将其转换成用户当前所在时区的时间,并按照对应的系统设置时间格式返回字符串结果。然而不同浏览器对用户本地所使用的语言格式的判断依据是不同的。
1
2
3
4
5
6
7
8
9
10
|
window
.
navigator
.
language
//"nl-NL"
window
.
navigator
.
systemLanguage
//"zh-CN"(设置中的非unicode程序所使用语言选项)
window
.
navigator
.
userLanguage
//"nl-NL"
window
.
navigator
.
browserLanguage
//"ja-JP"(系统菜单界面语言)
window
.
navigator
.
languages
//undefined
|
1
2
3
4
5
6
7
8
9
|
window
.
navigator
.
language
//'en-US'
window
.
navigator
.
languages
//["en-US", "zh-CN", "de", "zh", "en"]
//当界面语言改为"en-US",`accept-language`首位为`zh-CN`的时候
window
.
navigator
.
language
//'zh-CN'(`accept-language`首选值)
window
.
navigator
.
languages
//["zh-CN", "de", "zh", "en-US", "en"]
|
1
2
3
4
5
6
7
|
window
.
navigator
.
language
//'zh-CN'
window
.
navigator
.
languages
//["en-US", "en", "zh-CN", "zh", "ja", "zh-TW", "de-LI", "de", "pl"]
//当界面语言改为"en-US"时
window
.
navigator
.
language
//'en-US'(浏览器界面语言)
|
Moment.js为JavaScript Date对象提供了封装与统一好的API接口,并且提供了更多的功能。首先需要了解的是,Moment提供的moment对象是可变的,即当我们对该对象执行类似于增减或者设置的时候,其对象本身的值会发生变化,譬如下面这段代码:
1
2
3
4
|
var
a
=
moment
(
'2016-01-01'
)
;
var
b
=
a
.
add
(
1
,
'week'
)
;
a
.
format
(
)
;
"2016-01-08T00:00:00-06:00"
|
而如果我们不希望改变原有的值,特别是在需要创建多个时间日期对象的时候,我们可以利用clone方法:
1
2
3
4
|
var
a
=
moment
(
'2016-01-01'
)
;
var
b
=
a
.
clone
(
)
.
add
(
1
,
'week'
)
;
a
.
format
(
)
;
"2016-01-01T00:00:00-06:00"
|
笔者是习惯在Webpack中进行打包,类似于Node下的安装方式:
1
2
3
4
5
|
//安装
npm
install
moment
//使用
var
moment
=
require
(
'moment'
)
;
moment
(
)
.
format
(
)
;
|
如果你需要引入某个语言包,那么可以用如下方式:
1
2
3
|
var
moment
=
require
(
'moment'
)
;
require
(
'moment/locale/cs'
)
;
console
.
log
(
moment
.
locale
(
)
)
;
// cs
|
1
2
3
4
|
//毫秒
var
day
=
moment
(
1318781876406
)
;
//秒
var
day
=
moment
.
unix
(
1318781876
)
;
|
1
2
3
4
5
6
7
|
moment
(
"2010-10-20 4:30"
,
"YYYY-MM-DD HH:mm"
)
;
// parsed as 4:30 local time
moment
(
"2010-10-20 4:30 +0000"
,
"YYYY-MM-DD HH:mm Z"
)
;
// parsed as 4:30 UTC
moment
(
"2010 13"
,
"YYYY MM"
)
.
isValid
(
)
;
// false (not a real month)
moment
(
"2010 11 31"
,
"YYYY MM DD"
)
.
isValid
(
)
;
// false (not a real day)
moment
(
"2010 2 29"
,
"YYYY MM DD"
)
.
isValid
(
)
;
// false (not a leap year)
moment
(
"2010 notamonth 29"
,
"YYYY MMM DD"
)
.
isValid
(
)
;
// false (not a real month name)
|
1
2
3
4
5
6
7
8
9
10
|
moment
(
)
.
seconds
(
30
)
===
new
Date
(
)
.
setSeconds
(
30
)
;
moment
(
)
.
seconds
(
)
===
new
Date
(
)
.
getSeconds
(
)
;
moment
(
)
.
get
(
'year'
)
;
moment
(
)
.
get
(
'month'
)
;
// 0 to 11
moment
(
)
.
get
(
'date'
)
;
moment
(
)
.
get
(
'hour'
)
;
moment
(
)
.
get
(
'minute'
)
;
moment
(
)
.
get
(
'second'
)
;
moment
(
)
.
get
(
'millisecond'
)
;
|
1
2
3
4
5
6
7
8
9
|
moment
(
)
.
set
(
'year'
,
2013
)
;
moment
(
)
.
set
(
'month'
,
3
)
;
// April
moment
(
)
.
set
(
'date'
,
1
)
;
moment
(
)
.
set
(
'hour'
,
13
)
;
moment
(
)
.
set
(
'minute'
,
20
)
;
moment
(
)
.
set
(
'second'
,
30
)
;
moment
(
)
.
set
(
'millisecond'
,
123
)
;
moment
(
)
.
set
(
{
'year'
:
2013
,
'month'
:
3
}
)
;
|
1
2
3
4
5
6
7
8
9
10
11
|
moment
(
)
.
add
(
Number
,
String
)
;
moment
(
)
.
add
(
Duration
)
;
moment
(
)
.
add
(
Object
)
;
moment
(
)
.
add
(
7
,
'days'
)
;
moment
(
)
.
subtract
(
Number
,
String
)
;
moment
(
)
.
subtract
(
Duration
)
;
moment
(
)
.
subtract
|