写在前面
Date 类型平常用得不是很多,但一用到,对它的使用就感到不是很熟悉,每次都是强行百度一波,可以看出自己的基础不是很牢。所以最近决定静下心来好好回顾一下以前自己忽视的基础,下面是我对 Date 类型的一些总结和看法。
UTC 和 GMT 及 北京时间的关系
在介绍Date类型前,我们先来了解一下 UTC 和 GMT 及 北京时间的关系。
GMT 即「格林威治标准时间」(Greenwich Mean Time,简称G.M.T.),指位于英国伦敦郊区的皇家格林威治天文台的标准时间,因为本初子午线被定义为通过那里的经线。然而由于地球的不规则自转,导致GMT时间有误差,因此目前已不被当作标准时间使用。
UTC 是最主要的世界时间标准,是经过平均太阳时(以格林威治时间GMT为准)、地轴运动修正后的新时标以及以「秒」为单位的国际原子时所综合精算而成的时间。 UTC 比 GMT 来得更加精准。其误差值必须保持在0.9秒以内,若大于0.9秒则由位于巴黎的国际地球自转事务中央局发布闰秒,使 UTC 与地球自转周期一致。不过日常使用中,GMT 与 UTC 的功能与精确度是没有差别的,我们在文章中提到的 GMT 时间与 UTC 时间是一样的。
GMT = UTC
因为时区的问题北京时间和UTC时间有这样的关系
UTC + 8 = 北京时间
, 这个公式有助于我们后面理解 Date 类型为什么在不同方法下的转换结果不同。
定义
ECMAScript 中的 Date 类型是在早期 Java 中的 java.util.Date 类基础上构建的。为此 Date 类型使用自 UTC ( Coordinated Universal Time, 国际协调时间)1970年1月1日午夜(零时)开始经过的毫秒数来保存日期。在使用这种数据存储格式的条件下,Date()类型保存的日期能够精确到1970年1月1日之前或之后的100 000 000年。
我们可以这样理解,我们创建的一个 Date 对象中保存有一个 value,这个 value 的大小是从 UTC 时间1970年1月1日午夜至指定时间经过的毫秒数的大小。
这个值其实就是我们经常使用到的时间戳,需要注意的是js内的时间戳指的是指定时间到1970年1月1日00:00:00 UTC对应的毫秒数,和unix时间戳不是一个概念,后者表示秒数,差了1000倍。因此我们在转换时经常会遇到精度丢失的问题(暂时采用这种说法)。
创建方式
要创建一个日期对象,使用 new 操作符和 Date 构造函数即可,如下所示。
1 | var now = new Date(); // 获得当前时间 |
在调用 Date 构造函数而不传递参数的情况下,新创建的对象自动获取当前日期和时间。如果想要根据指定的日期和时间创建对象,必须传入该日期的毫秒数(即从 UTC 时间1970年1月1日午夜至指定时间经过的毫秒数。)听起来是不是有点头大,难道我们还要自己计算好毫秒数才能创建相应的时间对象吗?这样岂不是太麻烦了?
Date.parse() 和 Date.UTC()
针对上面的问题ECMAScript提供了两个方法 Date.parse() 和 Date.UTC() ,以此来简化这一计算过程。它们会根据我们传入的参数来自动计算出毫秒数的大小。下面我们来分别介绍一下这两个方法。
Date.parse()
Date.parse()方法接受一个表示日期的字符串参数,然后尝试根据这个字符串返回相应的毫秒数,如果传入的字符串不能表示将日期则返回NaN。因为ECMA-262没有定义这个方法应该支持那种日期格式,因此这个方法的行为通常是因地区而异。例如将地区设置为美国的浏览器通常都接受下列日期格式:
- “月/日/年”,如6/13/2004;
- “英文月名日,年”,如January12,2004;
- “英文星期几 英文月名 日 年 时:分:秒 时区”,如Tue May 25 2004 00:00:00 GMT-0700。
- ISO 8601扩展格式YYYY-MM-DDTHH:mm:ss.sssZ(例如2004-05-25T00:00:00)。只有兼容ECMAScript 5的实现支持这种格式。
例如,要为2004年5月25日创建一个日期对象,可以使用下面的代码:
1 | var someDate = new Date(Date.parse("May 25, 2004")); |
Date.UTC()
Date.UTC()方法同样也返回表示日期的毫秒数。但它需要的参数不是字符串,它的参数分别是年份,基于0的月份(0到11),日(1到31),小时(0到23),分钟,秒以及毫秒数。这些参数里边只有前两个参数是必需的,如果没有提供日值,则默认日值为1,其余参数未指定则默认为0。如下面的例子所示:
1 | //GMT时间2000年1月1日午夜零时: |
注意
UTC 日期指的是在没有时区偏差的情况下(将日期转换为GMT时间)的日期值。 Date.parse() 方法是基于本地时区建立的,而 Date.UTC() 方法是基于无时区偏差建立的。所以如果我们对两个方法传入相同的时间,我们会发现 Date.parse() 方法得到的毫秒数相对于 Date.UTC() 方法得到的毫秒数会多八个小时的毫秒数(这里的本地时区指的是北京时间)。
1
2
3
4
5
6
7//假设我们传入相同的时间2018年3月18日
Date.parse("3/18/2018"); // 1521302400000
Date.UTC(2018,2,18); //1521331200000
// 1521302400000 - 1521331200000 = 28800000 = 8 x 60 x 60 x 1000如果我们输入的日期值超过了正常的范围,在不同的浏览器中的会有不同的处理方式。例如在解析
"January 32,2007"
时,有的浏览器会将其解析为"February 1,2007"
。而Opera浏览器则倾向于插入当前月份的当前日期值,返回"January 当前日期值,2007"
。其实我们没有必要在创建一个 Date 对象的时候显式调用 Date.parse() 和 Date.UTC() 方法,因为将相应的参数传入构造函数后,它会根据参数的类型在后台自动调用Date.parse() 或 Date.UTC() 方法,这样得到日期和时间都是基于本地时区的,就算你存入的参数类型是 Date.UTC() 方法所需的参数类型,最后得到的结果还是基于本地时区的结果。
Date.now()
ES5添加了Date.now()方法,用来返回表示调用这个方法时的日期和时间的毫秒数。这个方法可以用来分析函数的运行时间,如下。
1 | // 取得开始时间 |
在不支持它的浏览器中,我们可以通过+
操作符获取Date对象的时间戳,也可以达到同样的目的。
1 | // 取得开始时间 |
继承的方法
和其他引用类型一样,Date 类型也重写了 toLocaleString() 、toString() 和 valueOf() 方法,但这些方法的返回值与其他类型中的方法不同。
Date 类型的 toLocalString() 方法会按照浏览器设置的时区相适应的格式返回日期和时间。这意味着时间格式中会包含AM和PM,但不会包含时区信息,具体的格式会因浏览器而异。
1
2
3var now = new Date();
console.log(now.toLocaleString()); // 2018/3/20 上午10:33:32Date 类型的 toString() 方法会返回带有时区信息的日期和时间,其中时间一般以军用时间(范围0到23)表示。
1
2
3var now = new Date();
console.log(now.toString()); // Tue Mar 20 2018 10:33:32 GMT+0800 (中国标准时间)Date 类型的 valueOf() 方法会返回日期的毫秒表示,也就是时间戳。因此我们可以使用比较操作符来比较日期。
1
2
3
4var date1 = new Date(2018,1,1);
var data2 = new Date(2018,3,18);
console.log(data1 < data2); // true在使用比较操作符,会隐式地调用 Date 对象的 valueOf() 方法,然后根据得到的毫秒数来进行比较。
其实在实际应用中,使用 toLocaleString() 和 toString() 来显示日期时间是没有什么价值的,因为它们的返回的日期格式在不同的浏览器里大相径庭,无法得到一致化的显示结果,而且得到的格式对用户的交互效果也不是很友好。
日期格式化方法
Date类型还有一些专门用来将日期转化为字符串的方法,不过与 toLocaleString() 和 toString() 的缺点一样,在平常的使用中没有多大价值,所以仅做一下了解就好。
- toDateString()——以特定于实现的格式显示星期几、月、日和年;
- toTimeString()——以特定于实现的格式显示时、分、秒和时区;
- toLocaleDateString()——以特定于地区的格式显示星期几、月、日和年;
- toLocaleTimeString()——以特定于实现的格式显示时、分、秒;
- toUTCString()——以特定于实现的格式完整的UTC日期。
日期/时间组件方法
上面我们已经提到了,Date类型本身的字符串格式化方法很鸡肋,在日常使用中用处不大,所以一般我们只有自己编写适用于项目的 format 方法,这时我们一般需要用到获取日期中特定部分的方法。方法有点多,如下:
- getTime()
返回表示日期的毫秒数;与valueOf()方法返回的值相同 - setTime(毫秒)
以毫秒数设置日期,会改变整个日期 - getFullYear()
取得4位数的年份(如2007而非仅07) - getUTCFullYear()
返回UTC日期的4位数年份 - setFullYear(年)
设置日期的年份。传入的年份值必须是4位数字(如2007而非仅07) - setUTCFullYear(年)
设置UTC日期的年份。传入的年份值必须是4位数字(如2007而非仅07) - getMonth()
返回日期中的月份,其中0表示一月,11表示十二月 - getUTCMonth()
返回UTC日期中的月份,其中0表示一月,11表示十二月 - setMonth(月)
设置日期的月份。传入的月份值必须大于0,超过11则增加年份 - setUTCMonth(月)
设置UTC日期的月份。传入的月份值必须大于0,超过11则增加年份 - getDate()
返回日期月份中的天数(1到31) - getUTCDate()
返回UTC日期月份中的天数(1到31) - setDate(日)
设置日期月份中的天数。如果传入的值超过了该月中应有的天数,则增加月份 - setUTCDate(日)
设置UTC日期月份中的天数。如果传入的值超过了该月中应有的天数,则增加月份 - getDay()
返回日期中星期的星期几(其中0表示星期日,6表示星期六) - getUTCDay()
返回UTC日期中星期的星期几(其中0表示星期日,6表示星期六) - getHours()
返回日期中的小时数(0到23) - getUTCHours()
返回UTC日期中的小时数(0到23) - setHours(时)
设置日期中的小时数。传入的值超过了23则增加月份中的天数 - setUTCHours(时)
设置UTC日期中的小时数。传入的值超过了23则增加月份中的天数 - getMinutes()
返回日期中的分钟数(0到59) - getUTCMinutes()
返回UTC日期中的分钟数(0到59) - setMinutes(分)
设置日期中的分钟数。传入的值超过59则增加小时数 - setUTCMinutes(分)
设置UTC日期中的分钟数。传入的值超过59则增加小时数 - getSeconds()
返回日期中的秒数(0到59) - getUTCSeconds()
返回UTC日期中的秒数(0到59) - setSeconds(秒)
设置日期中的秒数。传入的值超过了59会增加分钟数 - setUTCSeconds(秒)
设置UTC日期中的秒数。传入的值超过了59会增加分钟数 - getMilliseconds()
返回日期中的毫秒数 - getUTCMilliseconds()
返回UTC日期中的毫秒数 - setMilliseconds(毫秒)
设置日期中的毫秒数 - setUTCMilliseconds(毫秒)
设置UTC日期中的毫秒数 - getTimezoneOffset()
返回本地时间与 UTC 时间相差的分钟数。例如,美国东部标准时间返回300。在某地进入夏令时的情况下,这个值会有所变化
时间戳的获取
在平时我们需要用到最多的地方还是时间戳的获取,上面我们也提到了不少可以的得到时间戳的方法,下面做一个归纳。
- 使用 Date.now() 获取当前时间的毫秒数,只适用于当前时间。
- 使用 Date.parse() 获取指定时间的毫秒数,只适用于指定时间。
- 使用 Date.UTC() 获取指定时间的毫秒数,只适用于指定时间。
- 使用操作符
+
获取Date对象表示日期的毫秒数,都适用取决于Date对象。 - 使用 valueOf() 获取Date对象表示日期的毫秒数,都适用取决于Date对象。
- 使用 getTime() 获取Date对象表示日期的毫秒数,都适用取决于Date对象。
写在最后
花了一些时间将 Date 类型的基础知识,稍微总结了一下。这一块还有很多内容,以后有时间还会对 Date 对象的格式化这个一块进行补充。通过总结发现了很多自己以前习惯性忽略的一些东西。还是希望通过这样的方式,把 JavaScript 这一块的基础打牢一点。不着急慢慢来 :)
本篇文章纯属于个人的学习总结,如果文章中出现错误或不严谨的地方,希望大家能够指出,谢谢!