JavaScript高级程序设计(第4版)
上QQ阅读APP看书,第一时间看更新

5.1 Date

ECMAScript的Date类型参考了Java早期版本中的java.util.Date。为此,Date类型将日期保存为自协调世界时(UTC, Universal Time Coordinated)时间1970年1月1日午夜(零时)至今所经过的毫秒数。使用这种存储格式,Date类型可以精确表示1970年1月1日之前及之后285616年的日期。

要创建日期对象,就使用new操作符来调用Date构造函数:

    let now = new Date();

在不给Date构造函数传参数的情况下,创建的对象将保存当前日期和时间。要基于其他日期和时间创建日期对象,必须传入其毫秒表示(UNIX纪元1970年1月1日午夜之后的毫秒数)。ECMAScript为此提供了两个辅助方法:Date.parse()和Date.UTC()。

Date.parse()方法接收一个表示日期的字符串参数,尝试将这个字符串转换为表示该日期的毫秒数。ECMA-262第5版定义了Date.parse()应该支持的日期格式,填充了第3版遗留的空白。所有实现都必须支持下列日期格式:

❑ “月/日/年”,如"5/23/2019";

❑ “月名日,年”,如"May 23, 2019";

❑ “周几 月名 日 年 时:分:秒 时区”,如"Tue May 232019 00:00:00 GMT-0700";

❑ ISO 8601扩展格式“YYYY-MM-DDTHH:mm:ss.sssZ”,如2019-05-23T00:00:00(只适用于兼容ES5的实现)。

比如,要创建一个表示“2019年5月23日”的日期对象,可以使用以下代码:

    let someDate = new Date(Date.parse("May 23, 2019"));

如果传给Date.parse()的字符串并不表示日期,则该方法会返回NaN。如果直接把表示日期的字符串传给Date构造函数,那么Date会在后台调用Date.parse()。换句话说,下面这行代码跟前面那行代码是等价的:

    let someDate = new Date("May 23, 2019");

这两行代码得到的日期对象相同。

注意 不同的浏览器对Date类型的实现有很多问题。比如,很多浏览器会选择用当前日期替代越界的日期,因此有些浏览器会将"January 32, 2019"解释为"February 1, 2019"。Opera则会插入当前月的当前日,返回"January当前日,2019"。就是说,如果是在9月21日运行代码,会返回"January 21, 2019"。

Date.UTC()方法也返回日期的毫秒表示,但使用的是跟Date.parse()不同的信息来生成这个值。传给Date.UTC()的参数是年、零起点月数(1月是0,2月是1,以此类推)、日(1~31)、时(0~23)、分、秒和毫秒。这些参数中,只有前两个(年和月)是必需的。如果不提供日,那么默认为1日。其他参数的默认值都是0。下面是使用Date.UTC()的两个例子:

    // GMT时间2000年1月1日零点
    let y2k = new Date(Date.UTC(2000, 0));
    // GMT时间2005年5月5日下午5 点55 分55 秒
    let allFives = new Date(Date.UTC(2005, 4, 5, 17, 55, 55));

这个例子创建了两个日期。第一个日期是2000年1月1日零点(GMT),2000代表年,0代表月(1月)。因为没有其他参数(日取1,其他取0),所以结果就是该月第1天零点。第二个日期表示2005年5月5日下午5点55分55秒(GMT)。虽然日期里面涉及的都是5,但月数必须用4,因为月数是零起点的。小时也必须是17,因为这里采用的是24小时制,即取值范围是0~23。其他参数就都很直观了。

与Date.parse()一样,Date.UTC()也会被Date构造函数隐式调用,但有一个区别:这种情况下创建的是本地日期,不是GMT日期。不过Date构造函数跟Date.UTC()接收的参数是一样的。因此,如果第一个参数是数值,则构造函数假设它是日期中的年,第二个参数就是月,以此类推。前面的例子也可以这样来写:

    // 本地时间2000年1月1日零点
    let y2k = new Date(2000, 0);
    // 本地时间2005年5月5日下午5 点55 分55 秒
    let allFives = new Date(2005, 4, 5, 17, 55, 55);

以上代码创建了与前面例子中相同的两个日期,但这次的两个日期是(由于系统设置决定的)本地时区的日期。

ECMAScript还提供了Date.now()方法,返回表示方法执行时日期和时间的毫秒数。这个方法可以方便地用在代码分析中:

    // 起始时间
    let start = Date.now();
    // 调用函数
    doSomething();
    // 结束时间
    let stop = Date.now(),
    result = stop - start;

5.1.1 继承的方法

与其他类型一样,Date类型重写了toLocaleString()、toString()和valueOf()方法。但与其他类型不同,重写后这些方法的返回值不一样。Date类型的toLocaleString()方法返回与浏览器运行的本地环境一致的日期和时间。这通常意味着格式中包含针对时间的AM(上午)或PM(下午),但不包含时区信息(具体格式可能因浏览器而不同)。toString()方法通常返回带时区信息的日期和时间,而时间也是以24小时制(0~23)表示的。下面给出了toLocaleString()和toString()返回的2019年2月1日零点的示例(地区为"en-US"的PST,即Pacific Standard Time,太平洋标准时间):

    toLocaleString() -2/1/2019 12:00:00 AM
    toString() - Thu Feb 12019 00:00:00 GMT-0800 (Pacific Standard Time)

现代浏览器在这两个方法的输出上已经趋于一致。在比较老的浏览器上,每个方法返回的结果可能在每个浏览器上都是不同的。这些差异意味着toLocaleString()和toString()可能只对调试有用,不能用于显示。

Date类型的valueOf()方法根本就不返回字符串,这个方法被重写后返回的是日期的毫秒表示。因此,操作符(如小于号和大于号)可以直接使用它返回的值。比如下面的例子:

    let date1 = new Date(2019, 0, 1);     // 2019年1月1日
    let date2 = new Date(2019, 1, 1);     // 2019年2月1日
    console.log(date1 < date2); // true
    console.log(date1 > date2); // false

日期2019年1月1日在2019年2月1日之前,所以说前者小于后者没问题。因为2019年1月1日的毫秒表示小于2019年2月1日的毫秒表示,所以用小于号比较这两个日期时会返回true。这也是确保日期先后的一个简单方式。

5.1.2 日期格式化方法

Date类型有几个专门用于格式化日期的方法,它们都会返回字符串:

❑ toDateString()显示日期中的周几、月、日、年(格式特定于实现);

❑ toTimeString()显示日期中的时、分、秒和时区(格式特定于实现);

❑ toLocaleDateString()显示日期中的周几、月、日、年(格式特定于实现和地区);

❑ toLocaleTimeString()显示日期中的时、分、秒(格式特定于实现和地区);

❑ toUTCString()显示完整的UTC日期(格式特定于实现)。

这些方法的输出与toLocaleString()和toString()一样,会因浏览器而异。因此不能用于在用户界面上一致地显示日期。

注意 还有一个方法叫toGMTString(),这个方法跟toUTCString()是一样的,目的是为了向后兼容。不过,规范建议新代码使用toUTCString()。

5.1.3 日期/时间组件方法

Date类型剩下的方法(见下表)直接涉及取得或设置日期值的特定部分。注意表中“UTC日期”,指的是没有时区偏移(将日期转换为GMT)时的日期。

(续)