原文链接:http://javascript.info/date,translate with ❤️ by zhangbao.

接下来介绍一个新的内置对象:Date。它存储日期和时间,并提供了操作日期/时间的方法。

例如,我们可以使用它存储创建/修改的时间,或者估计时间,或者只是打印出当前时间。

创建

使用 new Date() 的形式创建一个新的 Date 对象,提供下列参数之一:

new Date()

没有参数——创建一个包含当前日期和时间的 Date 对象:

  1. let now = new Date();
  2. alert( now ); // 显示当前日期/时间

new Date(milliseconds)

用指定的从 UTC 1970 的 1 月 1 号 0 时开始到现在的毫秒数(milliseconds)所表示的时间来创建一个 Date 对象。

  1. // 0 表示 01.01.1970 UTC+0
  2. let Jan01_1970 = new Date(0);
  3. alert( Jan01_1970 );
  4. // 现在加 24 小时, 得到 02.01.1970 UTC+0
  5. let Jan02_1970 = new Date(24 * 3600 * 1000);
  6. alert( Jan02_1970 );

自 1970 年初以来所过去的毫秒数称为时间戳

它是一个日期的轻量级数字表示。我们总是可以使用 new Date(timestamp) 从时间戳创建一个日期。并且可以在已存在 Date 对象上使用 date.getTime() 方法来获得时间戳(见下)。

new Date(datestring)

如果只有一个参数,并且是字符串,那么它会经 Date.parse 算法解析(见下文)。

  1. let date = new Date("2017-01-26");
  2. alert(date); // Thu Jan 26 2017 ...

new Date(yerar, month, date, hours, minutes, seconds, ms)

在本地时区创建给定组件所表示的日期。只有头两个参数是必需的。

注意:

  • 日期必须是四位数:2013 OK,98 不是。

  • 月份是从 0 (一月)开始的,到 11(十二月)。

  • date 表示日期,如果没有默认就是 1 号。

  • 如果没有提供 hours/minutes/seconds/ms,他们被认为等于 0。

例如:

  1. new Date(2011, 0, 1, 0, 0, 0, 0); // // 1 Jan 2011, 00:00:00
  2. new Date(2011, 0, 1); // 一样的, 小时等项目默认是 0

最小精度是1 ms(1/1000秒):

  1. let date = new Date(2011, 0, 1, 2, 3, 4, 567);
  2. alert( date ); // 1.01.2011, 02:03:04.567

获得时间组件

有许多方法可以从日期对象 Date 访问年、月等信息。但在分类后,它们很容易被记住。

getFullYear()

获得年份(4位数)

getMonth()

获得月份,从 0 到 11

getDate()

获得日期,从 1 到 31,这个方法的名字看起来有点奇怪。

getHours(), getMinutes(), getSeconds(), getMilliseconds()

获得对应时间组件。

⚠️ 不是 getYear,而是 getFullYear()

许多 JavaScript 引擎实现了非标准方法 getYear(),这个这个方法已弃用了,它有时会返回 2 位数表示的年份,请永远不要使用它。获得年份请认准 getFullYear()

另外,我们可以获得周几:

getDay()

从0(周日)到 6(周六),得到周几。从周日开始算起,有些国家不是这样,但这不能改变。

上面的所有方法都返回相对于本地时区的组件。

这些方法也有对应的 UTC 版本,返回 UTC+0 时区的年、月、日等:getUTCFullYear()getUTCMonthgetUTCDay()。只要在 "get" 之后插入一个 "UTC" 即可。

如果您的本地时区相对于 UTC 时间发生了变化,那么下面的代码显示了不同的时间。

  1. // 当前时间
  2. let date = new Date();
  3. // 当前时间的小时
  4. alert( date.getHours() );
  5. // UTC+0 时区的小时 (伦敦时间没有夏令时)
  6. alert( date.getUTCHours() );

除了给定的方法之外,还有两个特殊的方法,它们没有 UTC 变体:

getTime()

返回日期的时间戳——UTC+0 时区从 1970 年 1 月 1 日到现在过去的时间戳表示。

getTimezoneOffset()

以分钟的形式,返回本地时区和 UTC 时间的差异:

  1. // 如果在 UTC-1 时区, 输出 60
  2. // 如果在 UTC+3 时区, 输出 -180
  3. alert( new Date().getTimezoneOffset() );

设置日期组件

下面的方法允许设置日期/时间组件:

除了 setTime() 之外,每一个方法都有对应的 UTC 变体,例如:setUTCHours()。

正如我们所看到的,有些方法可以同时设置同一个组件,例如 setHours,未提及的组件没有被修改。

例如:

  1. let today = new Date();
  2. today.setHours(0);
  3. alert(today); // still today, but the hour is changed to 0
  4. today.setHours(0, 0, 0, 0);
  5. alert(today); // still today, now 00:00:00 sharp.

自动校正

自动更正是日期对象 Date 的一个非常方便的特性。我们可以设置超出范围的值,它会自动调整自己。

例如:

  1. let date = new Date(2013, 0, 32); // 32 Jan 2013 ?!?
  2. alert(date); // ...is 1st Feb 2013!

超出范围的日期组件是自动分发的。

假设我们需要将“2016年2月28日”的日期增加 2 天。在闰年的情况下,可能是“3月2日”或“3月1日”。我们不需要去想它,直接加。

  1. let date = new Date(2016, 1, 28);
  2. date.setDate(date.getDate() + 2);
  3. alert( date ); // 1 Mar 2016

这个特性通常用于在给定的时间之后获得日期。举个例子,让我们来看看“未来 70 秒”的日期:

  1. let date = new Date();
  2. date.setSeconds(date.getSeconds() + 70);
  3. alert( date ); // shows the correct date

我们也可以设置零甚至负的值,例如:

  1. let date = new Date(2016, 0, 2); // 2 Jan 2016
  2. date.setDate(1); // set day 1 of month
  3. alert( date );
  4. date.setDate(0); // min day is 1, so the last day of the previous month is assumed
  5. alert( date ); // 31 Dec 2015

日期转数值,日期差值

把一个 Date 对象转换成数值的时候,相当于调用了 date.getTime() 函数,会得到当前时间的时间戳表示。

  1. let date = new Date();
  2. alert(+date); // 毫秒数, 等同于调用 date.getTime() 得到的值

重要的副作用是:日期可以被减去,结果是它们的时间戳之差。

这可以用于时间测量:

  1. let start = new Date(); // start counting
  2. // 搞些事情
  3. for (let i = 0; i < 100000; i++) {
  4. let doSomething = i * i * i;
  5. }
  6. let end = new Date(); // done
  7. alert( `The loop took ${end - start} ms` );

Date.now()

如果我们只想测量时差,不需要创建 Date 对象。

有一个特殊方法 Date.now() 返回当期时间的时间戳形式。

在语义上等于 new Date().getTime(),但它不会创建一个中间日期对象。所以它更快,也不会给垃圾收集器带来压力。

使用它多是为了便捷或者性能考虑,像是 JavaScript 的游戏或者是其他类型的应用等。

所以这可能更好:

  1. let start = Date.now(); // milliseconds count from 1 Jan 1970
  2. // do the job
  3. for (let i = 0; i < 100000; i++) {
  4. let doSomething = i * i * i;
  5. }
  6. let end = Date.now(); // done
  7. alert( `The loop took ${end - start} ms` ); // subtract numbers, not dates

标记

如果我们想要一个可靠的计算 CPU 消耗函数,我们应该小心。

例如,让我们来写一个计算两个日期之间时差的函数:哪一个更快?

  1. // we have date1 and date2, which function faster returns their difference in ms?
  2. function diffSubtract(date1, date2) {
  3. return date2 - date1;
  4. }
  5. // 或者
  6. function diffGetTime(date1, date2) {
  7. return date2.getTime() - date1.getTime();
  8. }

这两种方法完全相同,但是其中一个使用显式的 date.gettime() 来获取日期的时间戳标识,而另一个则依赖于一个日期到数字转换规则,他们的结果总是一样的。

那么,哪个更快呢?

第一个想法可能是连续运行很多次,并测量时间差异。对于我们的例子,函数很简单,所以我们要做大约10万次。

我们试试:

  1. function diffSubtract(date1, date2) {
  2. return date2 - date1;
  3. }
  4. function diffGetTime(date1, date2) {
  5. return date2.getTime() - date1.getTime();
  6. }
  7. function bench(f) {
  8. let date1 = new Date(0);
  9. let date2 = new Date();
  10. let start = Date.now();
  11. for (let i = 0; i < 100000; i++) f(date1, date2);
  12. return Date.now() - start;
  13. }
  14. alert( 'Time of diffSubtract: ' + bench(diffSubtract) + 'ms' );
  15. alert( 'Time of diffGetTime: ' + bench(diffGetTime) + 'ms' );

哇!使用 getTime() 要快得多!这是因为没有类型转换,所以引擎更容易优化。

好的,我们有测量方式了,但这还不是一个很好的基准。

想象一下,在运行 bench(diffSubtract) 的时候,CPU 正在并行地做一些事情,它正在获取资源。在运行 bench(diffGetTime) 的时候,工作已经完成了。

对于现代多进程操作系统来说,这是一个相当真实的场景。

因此,第一个基准测试的 CPU 资源将少于第二个基准。这可能会导致错误的结果。

对于更可靠的基准测试,整个包的基准测试应该多次重新运行。

这里是代码示例:

  1. function diffSubtract(date1, date2) {
  2. return date2 - date1;
  3. }
  4. function diffGetTime(date1, date2) {
  5. return date2.getTime() - date1.getTime();
  6. }
  7. function bench(f) {
  8. let date1 = new Date(0);
  9. let date2 = new Date();
  10. let start = Date.now();
  11. for (let i = 0; i < 100000; i++) f(date1, date2);
  12. return Date.now() - start;
  13. }
  14. let time1 = 0;
  15. let time2 = 0;
  16. // run bench(upperSlice) and bench(upperLoop) each 10 times alternating
  17. for (let i = 0; i < 10; i++) {
  18. time1 += bench(diffSubtract);
  19. time2 += bench(diffGetTime);
  20. }
  21. alert( 'Total time for diffSubtract: ' + time1 );
  22. alert( 'Total time for diffGetTime: ' + time2 );

现代的JavaScript引擎开始将高级的优化应用于“热代码”,它执行很多次(不需要优化很少执行的东西)。因此,在上面的例子中,第一次执行没有经过充分的优化。我们可能想要增加一个热的运行:

  1. // added for "heating up" prior to the main loop
  2. bench(diffSubtract);
  3. bench(diffGetTime);
  4. // now benchmark
  5. for (let i = 0; i < 10; i++) {
  6. time1 += bench(diffSubtract);
  7. time2 += bench(diffGetTime);
  8. }

⚠️做基准测试时请小心

现代的JavaScript引擎执行许多优化。与“正常使用”相比,他们可能会调整“人工测试”的结果,特别是当我们对非常小的东西进行基准测试时。因此,如果你真的想要了解性能,那么请研究JavaScript引擎是如何工作的。然后你就可能不需要微基准测试了。

关于V8的大量文章可以在 http://mrale.ph/ 中找到。

Date.parse:从字符串解析时间

Date.parse(str) 用于从一个表示时间的字符串中读取日期。

字符串的格式应该是:YYYY-MM-DDTHH:mm:ss.sssZ:

  • YYYY-MM-DD:是指日期:年-月-日。

  • 字符“T”是作为分隔符使用的。

  • HH:mm:ss.sss:表示时间:小时:分钟:秒钟:毫秒。

  • 可选的字符 ‘Z’ 表示时间区域的格式 +-hh:mm。单个字符 Z 表示 UTC+0 时区。

更短的变体也是可以的,像 YYYY-MM-DD、YYYY-MM 甚至是 YYYY。

调用 Date.parse(str) 将给定的字符串按照指定时间格式解析,然后返回时间戳(UTC+0 时区自 1970 年 1 月 1 日以来的毫秒数表示)。如果格式无效,返回 NaN。

例如:

  1. let ms = Date.parse('2012-01-26T13:51:50.417-07:00');
  2. alert(ms); // 1327611110417 (timestamp)

我们可以从时间戳中立即创建一个 new Date() 对象:

  1. let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') );
  2. alert(date);

总结

  • 在 JavaScript 中,日期和时间使用 Date 对象表示。我们不能创建“只包含日期”或“只包含时间”的对象:Date 对象总是携带这两部分。

  • 月份是从 0 开始表示的(是的,就是指 1 月)。

  • 周几是使用 getDay() 获取的,也是从 0 开始(表示周日)。

  • 当为 Date 对象设置了超出正常范围的参数时,会自动修正。对应加/减日期/月份/小时非常有用。

  • 日期对象可以用来相减,得出的是两个日期之间的时差(用毫秒表示)。这是因为日期对象会发生到数值(也就是时间戳)的转换。

  • 使用 Date.now() 快速获得当前时间的时间戳表示。

注意,与许多其他系统不同的是,JavaScript 中的时间戳是以毫秒为单位的,而不是使用秒。

而且,有时我们需要更精确的时间测量。JavaScript 本身并没有一种方法来测量微秒(一百万分之一秒)的时间,但是大多数环境都提供了它。例如,浏览器有 performance.now() 方法用来测试从页面加载开始的毫秒数和微秒级的精度(点之后 3 位):

  1. alert(`Loading started ${performance.now()}ms ago`);
  2. // Something like: "Loading started 34731.26000000001ms ago"
  3. // .26 is microseconds (260 microseconds)
  4. // more than 3 digits after the decimal point are precision errors, but only the first 3 are correct

Node.js 有 microtime 模块和其他方法。从技术上讲,任何设备和环境都可以获得更精确的精确度,不只是用 Date 表示。

(完)