写在前面
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 构造函数即可,如下所示。
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日创建一个日期对象,可以使用下面的代码:
var someDate = new Date(Date.parse("May 25, 2004"));
Date.UTC()
Date.UTC()方法同样也返回表示日期的毫秒数。但它需要的参数不是字符串,它的参数分别是年份,基于0的月份(0到11),日(1到31),小时(0到23),分钟,秒以及毫秒数。这些参数里边只有前两个参数是必需的,如果没有提供日值,则默认日值为1,其余参数未指定则默认为0。如下面的例子所示:
//GMT时间2000年1月1日午夜零时:
var y2k = new Date(Date.UTC(2000, 0));
//GMT时间2005年5月5日下午5:55:55:
var allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));
注意
//假设我们传入相同的时间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()方法,用来返回表示调用这个方法时的日期和时间的毫秒数。这个方法可以用来分析函数的运行时间,如下。
// 取得开始时间
var start = Date.now();
// 调用函数
doSomething();
// 获取结束时间
var end = Date.now();
// 得到函数运行时间
var runtime = start - end;
在不支持它的浏览器中,我们可以通过+
操作符获取Date对象的时间戳,也可以达到同样的目的。
// 取得开始时间
var start = +new Date();
// 调用函数
doSomething();
// 获取结束时间
var end = +new Date();
// 得到函数运行时间
var runtime = start - end;
继承的方法
和其他引用类型一样,Date 类型也重写了 toLocaleString() 、toString() 和 valueOf() 方法,但这些方法的返回值与其他类型中的方法不同。
var now = new Date();
console.log(now.toLocaleString()); // 2018/3/20 上午10:33:32
var now = new Date();
console.log(now.toString()); // Tue Mar 20 2018 10:33:32 GMT+0800 (中国标准时间)
var date1 = new Date(2018,1,1);
var data2 = new Date(2018,3,18);
console.log(data1
在使用比较操作符,会隐式地调用 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 这一块的基础打牢一点。不着急慢慢来 :)