一些概念

Date是什么?

Date是JavaScript用来操作日期时间的对象,以常规函数调用它,会返回一个代表当前时间的字符串,而不是一个日期对象,即:

typeof Date()          // "string"
typeof (new Date())    // "object"

除了文献[6]中提到的返回是否依赖参数之外,返回的类型也有差别,因此,可以更严谨地说明:Date()无论参数如何都只返回当前时间的字符串,而new Date()则会根据参数返回相应时间对象(打印出来的是toString方法执行的结果)。

JavaScript的时间戳又是什么?

JavaScript的时间戳定义为从格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起到现在的总毫秒数,对于每一个时刻而言,都有独一无二的时间戳与它对应。

时间戳,无关乎时区,对于同一时刻而言,不同时区虽然表现的时间不相同,但时间戳是相同的。
想象这样一个场景:当前是格林威治时间2018-07-20 00:00:00,服务端位于中国(东八区,时间是2018-07-20 08:00:00),前端位于洛杉矶(西七区,时间是2018-07-19 18:00:00),而前端的页面想要显示出服务端的日期,如果两端之间通过时间戳1532044800000来传递,服务端是7月20日,而客户端显示的则是7月19日,这并不符合我们的预期,所以正确的应该传递服务端所在时区的日期“2018-07-20”才能满足我们的需求。

UTC 与 GMT

UTC(Coordinated Universal Time,以UTC为缩写是英文与法文的折衷缩写)协调世界时间,是标准时间,以经过平均太阳时(以格林威治时间GMT为准),地轴运动修正后的新时标以及以“秒”为单位的国际原子时所综合精算而成的时间。不过因为时区的影响,没有国家会直接使用它作为当地时间的。

GMT(Greenwich Mean Time,以GMT为缩写)格林威治时间,实际上是一个时区(time zone),以时间来看的话,是英国伦敦郊区的皇家格林威治天文台的标准时间(零时区),由于零时区的特殊性常与UTC混用,因此在使用上GMT与UTC代表的也是相同的时间。

Date构造函数的参数们

Date构造函数的参数可以分为四类:

  • 无参数
  • (单参数)时间戳
  • (单参数)时间字符串
  • 至少三个参数以上,代表年、月、日、时、分、秒、毫秒

无参数、多参数和时间戳的情况比较简单,注意传参的类型是Number就行了,而字符串的方式则比较复杂,我们单独拿出来研究。

时间字符串格式标准共有两种:ISO 8601和RFC 2822。

ISO 8601标准[3]

ISO 8601标准格式“YYYY-MM-DDTHH:mm:ss.sssTZD”,其中:

  • YYYY-MM-DD是年月日,HH:mm:ss.sss是时分秒毫秒;
  • TZD代表时区(Chrome默认为“Z”,Safari默认为当前时区),可以是“Z”即为标准时间UTC,或者是“±hh:mm”;
  • T是日期与时间的区分:
    • 如果T换成空格,Chrome中则与有T时表示的相同,Safari则作为“Invalid Date”

举个栗子,对比一下2018年7月20日零点在东八区(或默认时区)和GMT的不同:

new Date('2018-07-20T00:00:00+00:00')     // Fri Jul 20 2018 08:00:00 GMT+0800 (中国标准时间) --> 标准时间的0点对应东八区的8点
new Date('2018-07-20T00:00:00+08:00')     // Fri Jul 20 2018 00:00:00 GMT+0800 (中国标准时间) --> 与 new Date('2018-07-20T00:00:00')一致

new Date('2018-07-20 00:00:00')        // Chrome中:Fri Jul 20 2018 00:00:00 GMT+0800 (中国标准时间)
new Date('2018-07-20 00:00:00')        // Safari中:Invalid Date

RFC 2822标准[4]

RFC2822标准格式“day-of-week DD month-name YYYY HH:mm:ss ±hhmm”,但DD与month-name交换的格式也是可以被识别的。

举个栗子,Chrome和Safari表示时间的格式就是这个标准,如:

new Date('Fri 20 Jul 2018 00:00:00 +0800')    // Fri Jul 20 2018 00:00:00 GMT+0800 (中国标准时间)

另外,还有一种格式“YYYY/MM/DD”,我没找到标准文档,因为文献[5]中提到与ISO8601日期格式在默认时区上有些不同,这里也单独列出来:

new Date('2018/07/21')        // Sat Jul 21 2018 00:00:00 GMT+0800 (中国标准时间)
new Date('2018-07-21')        // Sat Jul 21 2018 08:00:00 GMT+0800 (中国标准时间)

相同的情况,文献[2]也有说明:

given an ISO format such as "2014-03-07" it will assume a time zone of UTC (ES5 and ECMAScript 2015).

Date的getter们

有了本文前面的知识,Date的getter会更容易理解一些。

  • toISOString:返回完整的ISO8601格式,如“2018-07-19T16:00:00.000Z”;
  • toUTCString(toGMTString):以RFC2822格式返回标准时间的时间字符串,如“Thu, 19 Jul 2018 16:00:00 GMT”;
  • toLocaleDateString:返回当前时区的日期部分,以“YYYY/MM/DD”这种格式,如“2018/7/20”;
  • toLocaleTimeString:返回当前时区的时间部分,以“上午/下午 hh:mm:ss”格式,如“上午 12:00:00”(这里指的其实是0点);
  • toLocaleString:返回当前时区的完整时间,如“2018/7/20 上午12:00:00”;
  • toDateString:返回RFC2822格式中日期部分,如“Fri Jul 20 2018”;
  • toTimeString:返回RFC2822格式中时间部分,如“00:00:00 GMT+0800 (中国标准时间)”
  • toString:返回RFC2822格式的完整时间,如“Fri Jul 20 2018 00:00:00 GMT+0800 (中国标准时间)”;
  • valueOf:返回Number格式的时间戳,如“1532016000000”;

总结

在时间的漩涡里挣扎了一个多星期,终于把文章写完了。这一切还是源于“JavaScript的时间戳又是什么?”里提到的场景,原本我天真的以为每个时区都有自己的时间戳,但实际上并非如此。会有这样的偏差也是概念不够清晰理解的缘故,当我意识到这点就开始找资料调研起Date对象。标准文档还是一样抽象难啃,更坑爹的是一个小小的Date对象都有这么多的标准和差异,感受真的如同文献5的标题“What A Mess!”。

最后列一下浏览器版本吧:

  • Chrome版本:版本 67.0.3396.99(正式版本) (64 位)
  • Safari版本:11.0.2 (13604.4.7.1.6)

参考文献

  1. 《The Difference Between GMT and UTC》https://www.timeanddate.com/t...
  2. https://developer.mozilla.org...
  3. ISO 8601 https://www.w3.org/TR/NOTE-da...
  4. RFC 2822 https://tools.ietf.org/html/r...
  5. 《JavaScript and Dates, What a Mess!》http://blog.dygraphs.com/2012... 
  6. 《JS原生Date类型方法的一些冷知识》https://segmentfault.com/a/11...

_west
318 声望11 粉丝

若我自己不够好,谁会来拥抱