JS原生Date类型方法的一些冷知识
一个多月没更新了- -偷懒中。这个东西其实很早之前就在整理了,不过后来发现自己不少地方没弄明白,然后就一直卡那边了(其实就是不想写吧),想了下反正是给自己熟悉js的原生API而已,所以也没必要太钻牛角尖,也不一定要多完整,因此就当是Date()
函数的一个冷门知识点小补充吧。这篇文章主要讲Date()的字符串与时间戳转换以及用户时间本地化,可能内容上比较乱(不然也不会卡我一个月时间了),见谅
ps:由于Date()
是js原生函数,不同浏览器的解析器对其实现方式并不同,所以返回值也会有所区别。本文测试未特别申明浏览器的情况下,均是指win7 x64
+chrome 44.0.2403.155 (正式版本) m (32 位)
版本
Date()与new Date()的区别
Date()
直接返回当前时间字符串,不管参数是number还是任何string
Date();Date('sssss');Date(1000);//Fri Aug 21 2015 15:46:21 GMT+0800 (中国标准时间)
而new Date()
则是会根据参数来返回对应的值,无参数的时候,返回当前时间的字符串形式;有参数的时候返回参数所对应时间的字符串。new Date()
对参数不管是格式还是内容都要求,且只返回字符串,
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 Datenew Date('2011-01-01T11:00:00')-new Date('1992/02/11 12:00:12')//596069988000
从上面几个测试结果可以很容易发现
new Date()
在参数正常的情况只会返回当前时间的字符串(且是当前时区的时间)new Date()
在解析一个具体的时间的时候,对参数有较严格的格式要求,格式不正确的时候会直接返回Invalid Date
,比如将number
类的时间戳转换成string
类的时候也会导致解析出错虽然
new Date()
的返回值是字符串,然而两个new Date()
的结果字符串是可以直接相减的,结果为相差的毫秒数。
那么,new Date()
能接受的参数格式到底是什么标准呢?(相对于严格要求的多参数传值方法。非严格的单参数(数字日期表示格式)更常用且更容易出错,所以下文只考虑单参数数字时间字符串转换的情况)
new Date()解析所支持的参数格式标准
时间戳格式
这个是最简单的也是最不容易出错的。当然唯一的缺点大概就是对开发者不直观,无法一眼看出具体日期。
需要注意的以下两点:
js内的时间戳指的是当前时间到
1970年1月1日00:00:00 UTC
对应的毫秒数,和unix时间戳不是一个概念,后者表示秒数,差了1000倍
new Date(timestamp)
中的时间戳必须是number
格式,string
会返回Invalid Date
。所以比如new Date('11111111')
这种写法是错的
时间数字字符串格式
不大清楚这种该怎么描述,就是类似YYYY/MM/DD HH:mm:SS
这种。下文以dateString
代指。new Date(dateString)
所支持的字符串格式需要满足RFC2822标准或者ISO 8601标准
这两种标准对应的格式分别如下:
RFC2822 标准日期字符串
YYYY/MM/DD HH:MM:SS ± timezon(时区用4位数字表示)// eg 1992/02/12 12:23:22+0800
RFC2822还有别的格式,不过上面这个是比较常用的(另外这标准太难啃了,实在没耐心啃完,所以也就没太深入)。RFC2822标准本身还有其他的非数字日期表达方式,不过不在这个话题讨论范围内了,略过
ISO 8601标准日期字符串
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
日期和时间中间的
T
不可以被省略,一省略就出错。虽然在chrome浏览器上时区也可以用
+0100
这种RFC2822的形式来表示,然而IE上不支持这种混搭写法,所以用ISO8601标准形式表示的时候时区要用+HH:MM
单单从格式上来说,两者的区别主要在于分隔符的不同。不过需要注意的是,ISO 8601标准的兼容性比RFC2822差得多(比如IE8和iOS均不支持前者。我知道IE8很多人会无视,不过iOS也有这个坑的话,各位或多或少会谨慎点了吧?),所以一般情况下建议用RFC 2822
格式的。
不过需要注意的是,在未指定时区的前提下,对于只精确到day
的日期字符串,RFC 2822
返回结果是以当前时区的零点
为准,而ISO8601
返回结果则会以UTC时间
的零点为标准进行解析。
例如:
//RFC2822:new Date('1992/02/13') //Thu Feb 13 1992 00:00:00 GMT+0800 (中国标准时间)//ISO8601:new Date('1992-02-13') //Thu Feb 13 1992 08:00:00 GMT+0800 (中国标准时间)
然而上面这个只是ES5的标准而已,在ES6里这两种形式都会变成当前时区的零点
为基准1不管你们崩溃没,反正我是已经想死了
关于跨浏览器的dataString解析情况,还可以参考这个页面:
JavaScript and Dates, What a Mess!
所以对于时间字符串对象,个人意见是要么用RFC2822
形式,要么自己写个解析函数然后随便你传啥格式进来。
时间格式化函数的效率
这里的时间格式化
值得是将时间字符串转换成毫秒数的过程。js原生的时间格式化函数有Date.parse
、Date.prototype.valueOf
、Date.prototype.getTime
、Number(Date)
、+Date
(还有个Date.UTC
方法,然而对参数要求严格,不能直接解析日期字符串,所以略过)
这5个函数从功能上来说一模一样,但是具体的效率如何呢?我写了个检测页面,诸位也可以自己测试下。
See the Pen efficiency-of-time-format-function by chitanda (@chitanda) on CodePen.
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>
核心测试函数:
function test(dateString,times,func){ var startTime=window.performance.now(); // console.log('start='+startTime.getTime()); for (var i = 0; i < times; i++) { func(dateString);//这里填写具体的解析函数 }; var endTime=window.performance.now(); // console.log('endTime='+endTime.getTime()); var gapTime=endTime-startTime; console.log('一共耗时:'+gapTime+'ms'); // console.log('时间字符串'+dateString); return gapTime;}
之所以这里用
window.performance.now()
而不用new Date()
,是因为前者精确度远比后者高。后者只能精确到ms。会对结果造成较大影响
测试结果:
单次执行50W次时间格式化函数,并重复测试100次,最后的结果如下:
(表格中的数字为单次执行50W次函数的平均结果。单位为毫秒)
函数 | chrome | IE | Firefox |
---|---|---|---|
Date.parse() | 151.2087 | 55.5811 | 315.0446 |
Date.prototype.getTime() | 19.5452 | 21.3423 | 14.0169 |
Date.prototype.valueOf() | 20.1696 | 21.7192 | 13.8096 |
+Date() | 20.0044 | 31.3511 | 22.7861 |
Number(Date) | 23.0900 | 24.8838 | 23.3775 |
从这个表格可以很容易得出以下结论:
从计算效率上来说,
Date.prototype.getTime()
≈Date.prototype.valueOf()
>
+Date
≈Number(Date)
>>Date.parse()
从代码书写效率上来说,对于少量的时间格式化计算,用
+Date()
或者Number(Date)
即可。而若页面内有大量该处理,则建议用Date原生的函数Date.prototype.getTime()
或者Date.prototype.valueOf()
.只有Date.parse
,找不到任何使用的理由。这个结果和计算机的计算性能以及浏览器有关,所以具体数字可能会有较大偏差,很正常。然而几个函数结果的时间差大小顺序并不会变。
codepen的在线demo限制比较大,对于这个测验个人建议最好将源代码复制到本地文件然后进行测试
UTC,GMT时间的区别
这个不是啥重要东西,单纯当课外知识吧。
格林威治标准时间GMT
GMT即「格林威治标准时间」(Greenwich Mean Time,简称G.M.T.),指位于英国伦敦郊区的皇家格林威治天文台的标准时间,因为本初子午线被定义为通过那里的经线。然而由于地球的不规则自转,导致GMT时间有误差,因此目前已不被当作标准时间使用。
世界协调时间UTC
UTC是最主要的世界时间标准,是经过平均太阳时(以格林威治时间GMT为准)、地轴运动修正后的新时标以及以「秒」为单位的国际原子时所综合精算而成的时间。UTC比GMT来得更加精准。其误差值必须保持在0.9秒以内,若大于0.9秒则由位于巴黎的国际地球自转事务中央局发布闰秒,使UTC与地球自转周期一致。不过日常使用中,GMT与UTC的功能与精确度是没有差别的。
协调世界时区会使用“Z”来表示。而在航空上,所有使用的时间划一规定是协调世界时。而且Z在无线电中应读作“Zulu”(可参见北约音标字母),协调世界时也会被称为“Zulu time”。
浏览器获取用户当前时间以及喜好语言
首先需要注意一点,浏览器获取当前用户所在的时区等信息只和系统的日期和时间
设置里的时区以及时间有关。区域和语言
设置影响的是浏览器默认时间函数(Date.prototype.toLocaleString等)显示的格式,不会对时区等有影响。以window为例,控制面板\时钟、语言和区域
中的两个子设置项目的区别如下:
日期和时间
:设置当前用户所处的时间和时区,浏览器获取到的结果以此为准,哪怕用户的设置时间和时区是完全错误的。比如若东八区的用户将自己的时区设置为东9区,浏览器就会将视为东9区;时间数据上同理。这里的设置会影响Date.prototype.getTimezoneOffset
、new Date()
的值
区域和语言
:主要是设置系统默认的时间显示方式。其子设置的格式
会影响Date.prototype.toLocaleString
方法返回的字符串结果
浏览器判断用户本地字符串格式
Date有个Date.prototype.toLocaleString()
方法可以将时间字符串返回用户本地字符串格式,这个方法还有两个子方法Date.prototype.toLocaleDateString
和Date.prototype.toLocaleTimeString
,这两个方法返回值分别表示日期
和时间
,加一起就是Date.prototype.toLocaleString
的结果。
这个方法的默认参数会对时间字符串做一次转换,将其转换成用户当前所在时区的时间,并按照对应的系统设置时间格式返回字符串结果。然而不同浏览器对用户本地所使用的语言格式的判断依据是不同的。
IE:获取系统当前的区域和语言
-格式
中设置的格式,依照其对应的格式来显示当前时间结果;IE浏览器实时查询该系统设置(即你在浏览器窗口打开后去更改系统设置也会引起返回格式变化)
FF:获取方式和结果与IE浏览器相同,区别在于FF只会在浏览器进程第一次启动的时候获取一次系统设置,中间不管怎么系统设置怎么变化,FF都无法获取到当前系统设置。除非重启FF浏览器。
Chrome:获取方式和以上两个都不同。chrome无视系统的区域和语言
-格式
格式,只依照自己浏览器的界面设置的菜单语言来处理。(比如英文界面则按系统'en-US'格式返回字符串,中文界面则按系统'zh-CN'格式返回结果)
综上可得:
chrome下浏览器语言设置优先系统语言设置。而IE和FF则是系统语言设置优先浏览器语言设置,不管浏览器界面语言是什么,他们只依照系统设置来返回格式。(没有MAC,所以不知道safari是啥情况,等以后看情况补充吧)
另外,不同浏览器对toLocaleString
返回的结果也是不同的,IE浏览器严格遵守系统设置,而chrome和FF会有自己内置的格式来替换。
浏览器界面语言设置和语言设置的区别
这小节貌似有点跑题,然而不说明下的很容易和上面提到的浏览器设置的语言混淆,所以也拿出来说一下。
需要注意浏览器的语言设置和界面语言设置不是一回事。浏览器的语言设置
设置的是浏览器发送给服务器的Request Header
里的Accept-Language
的值,这个值可以告诉服务器用户的喜好语言,对于某些跨国网站,服务器可以以此为依旧来返回对应语言的页面(不过实际应用上这个限制比较大,大部分网站还是根据IP来判断用户来源的,或者直接让用户自己选择)
对于各大浏览器而言,这个设置的更改也是比较显性,容易找到的。
IE: Internet选项
-语言
FF: 选项
-内容
-语言
chrome:设置
-显示高级设置
-语言
-语言和输入设置...
上面这里的设置不会影响到浏览器的界面语言设置,以国内大部分用户而言,即不管你怎么设置这里的语言选项,浏览器菜单等默认都会是以中文显示的.
而浏览器的界面语言设置
一般来说则藏的深得多,没那么容易找到。
IE:
卸载前面安装过的浏览器语言包,去微软官网下载对应的IE浏览器语言包安装。(和安装的语言包有关。系统界面语言和该语言包相同的情况下,变为该语言。否则以安装的语言包为准。)
FF:地址栏输入about:config
,然后找到general.useragent.locale
字段,修改对应字段即可。
chrome:设置
-显示高级设置
-语言
-语言和输入设置...
利用js获取用户浏览器语言喜好
对于获取这两种设置,js原生方法支持度都比较一般:
IE下的navigator
方法有四种和language
有关的方法,区别如下:
假设系统语言为 ja-JP
,系统unicode语言为zh-CN
日期格式为nl-NL
,浏览器语言设置(accept-language)为de
,浏览器界面语言为en-US
(其他条件不变,浏览器界面语言改为zh-CN
的时候结果也是一样),
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
chrome下,当浏览器界面语言为zh-CN
,accept-language
首位为en-US
的时候:
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'(浏览器界面语言)
FF下,当浏览器界面语言为zh-CN
,accept-language
首位为en-US
的时候:
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"]
从上面的测试结果可以很明显的发现IE浏览器的这几个函数都是获取系统信息的,无法获取到前面提到的两个浏览器层面上的设置。
这几个函数具体含义还有疑问的可以参考MSDN官方文档
window.navigator.language
这个函数虽然三个浏览器都可以兼容,然而代表的意义完全不同。IE下该函数返回系统设置的时间显示格式所遵守的标准的地区代码;chrome下返回浏览器界面语言;FF下返回accept-language
的首选语言值
由此:
浏览器设置的语言
即accept-language
值,IE浏览器无法利用JS获取。chrome和FF浏览器都可以利用window.navigator.languages
来获取,而FF还可以直接用window.navigator.language
直接获取accept-language
的首选语言值。
所以对于accept-language
,兼容性最好的获取方法应该是利用后端,发起一个ajax请求,分析header。而不是直接js来处理。
浏览器界面语言
,IE和FF都无法利用js来获取,chrome可以用window.navigator.language
来获取系统级别的语言设置(系统菜单界面语言,系统设置的时间显示格式),chrome和FF都无法用JS获取到
总结
这篇文章断断续续地写了一个多月,不过由于对Date()
函数的掌握不足因此个人感觉其实还是思路有点乱,所以文章看起来可能稍微有点跳跃性。不过用户本地化那块内容确实用了不少心思去写,希望对看到这篇文章的人有点帮助。
参考文献
Date and Time Formats
Date and Time Specification(RFC2822)
Date.parse()-Differences in assumed time zone
JavaScript and Dates, What a Mess!
navigator object(IE浏览器私有language函数的解析)