JavaScript深入理解之Date类型详解

JavaScript深入理解之Date类型详解 文章配图

写在前面

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
2
3
4
5
//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));

注意

  • 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
2
3
4
5
6
7
8
9
10
11
// 取得开始时间
var start = Date.now();

// 调用函数
doSomething();

// 获取结束时间
var end = Date.now();

// 得到函数运行时间
var runtime = start - end;

在不支持它的浏览器中,我们可以通过+操作符获取Date对象的时间戳,也可以达到同样的目的。

1
2
3
4
5
6
7
8
9
10
11
// 取得开始时间
var start = +new Date();

// 调用函数
doSomething();

// 获取结束时间
var end = +new Date();

// 得到函数运行时间
var runtime = start - end;

继承的方法

和其他引用类型一样,Date 类型也重写了 toLocaleString() 、toString() 和 valueOf() 方法,但这些方法的返回值与其他类型中的方法不同。

  • Date 类型的 toLocalString() 方法会按照浏览器设置的时区相适应的格式返回日期和时间。这意味着时间格式中会包含AM和PM,但不会包含时区信息,具体的格式会因浏览器而异

    1
    2
    3
    var now = new Date();

    console.log(now.toLocaleString()); // 2018/3/20 上午10:33:32
  • Date 类型的 toString() 方法会返回带有时区信息的日期和时间,其中时间一般以军用时间(范围0到23)表示。

    1
    2
    3
    var now = new Date();

    console.log(now.toString()); // Tue Mar 20 2018 10:33:32 GMT+0800 (中国标准时间)
  • Date 类型的 valueOf() 方法会返回日期的毫秒表示,也就是时间戳。因此我们可以使用比较操作符来比较日期。

    1
    2
    3
    4
    var 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 这一块的基础打牢一点。不着急慢慢来 :)

本篇文章纯属于个人的学习总结,如果文章中出现错误或不严谨的地方,希望大家能够指出,谢谢!

坚持原创技术分享,您的支持将鼓励我继续创作!