原文链接:http://javascript.info/date,translate with ❤️ by zhangbao.
接下来介绍一个新的内置对象:Date。它存储日期和时间,并提供了操作日期/时间的方法。
例如,我们可以使用它存储创建/修改的时间,或者估计时间,或者只是打印出当前时间。
创建
使用 new Date() 的形式创建一个新的 Date 对象,提供下列参数之一:
new Date()
没有参数——创建一个包含当前日期和时间的 Date 对象:
let now = new Date();
alert( now ); // 显示当前日期/时间
new Date(milliseconds)
用指定的从 UTC 1970 的 1 月 1 号 0 时开始到现在的毫秒数(milliseconds)所表示的时间来创建一个 Date 对象。
// 0 表示 01.01.1970 UTC+0
let Jan01_1970 = new Date(0);
alert( Jan01_1970 );
// 现在加 24 小时, 得到 02.01.1970 UTC+0
let Jan02_1970 = new Date(24 * 3600 * 1000);
alert( Jan02_1970 );
自 1970 年初以来所过去的毫秒数称为时间戳。
它是一个日期的轻量级数字表示。我们总是可以使用 new Date(timestamp) 从时间戳创建一个日期。并且可以在已存在 Date 对象上使用 date.getTime() 方法来获得时间戳(见下)。
new Date(datestring)
如果只有一个参数,并且是字符串,那么它会经 Date.parse 算法解析(见下文)。
let date = new Date("2017-01-26");
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。
例如:
new Date(2011, 0, 1, 0, 0, 0, 0); // // 1 Jan 2011, 00:00:00
new Date(2011, 0, 1); // 一样的, 小时等项目默认是 0
最小精度是1 ms(1/1000秒):
let date = new Date(2011, 0, 1, 2, 3, 4, 567);
alert( date ); // 1.01.2011, 02:03:04.567
获得时间组件
有许多方法可以从日期对象 Date 访问年、月等信息。但在分类后,它们很容易被记住。
获得年份(4位数)
获得月份,从 0 到 11。
获得日期,从 1 到 31,这个方法的名字看起来有点奇怪。
getHours(), getMinutes(), getSeconds(), getMilliseconds()
获得对应时间组件。
⚠️ 不是 getYear,而是 getFullYear()
许多 JavaScript 引擎实现了非标准方法 getYear()
,这个这个方法已弃用了,它有时会返回 2 位数表示的年份,请永远不要使用它。获得年份请认准 getFullYear()
。
另外,我们可以获得周几:
从0(周日)到 6(周六),得到周几。从周日开始算起,有些国家不是这样,但这不能改变。
上面的所有方法都返回相对于本地时区的组件。
这些方法也有对应的 UTC 版本,返回 UTC+0 时区的年、月、日等:getUTCFullYear()、getUTCMonth、getUTCDay()。只要在 "get"
之后插入一个 "UTC"
即可。
如果您的本地时区相对于 UTC 时间发生了变化,那么下面的代码显示了不同的时间。
// 当前时间
let date = new Date();
// 当前时间的小时
alert( date.getHours() );
// UTC+0 时区的小时 (伦敦时间没有夏令时)
alert( date.getUTCHours() );
除了给定的方法之外,还有两个特殊的方法,它们没有 UTC 变体:
返回日期的时间戳——UTC+0 时区从 1970 年 1 月 1 日到现在过去的时间戳表示。
以分钟的形式,返回本地时区和 UTC 时间的差异:
// 如果在 UTC-1 时区, 输出 60
// 如果在 UTC+3 时区, 输出 -180
alert( new Date().getTimezoneOffset() );
设置日期组件
下面的方法允许设置日期/时间组件:
setTime(milliseconds)(自 UTC 1970 年 1 月 1 日以来的时间,用此毫秒数设置整个日期)
除了 setTime() 之外,每一个方法都有对应的 UTC 变体,例如:setUTCHours()。
正如我们所看到的,有些方法可以同时设置同一个组件,例如 setHours,未提及的组件没有被修改。
例如:
let today = new Date();
today.setHours(0);
alert(today); // still today, but the hour is changed to 0
today.setHours(0, 0, 0, 0);
alert(today); // still today, now 00:00:00 sharp.
自动校正
自动更正是日期对象 Date 的一个非常方便的特性。我们可以设置超出范围的值,它会自动调整自己。
例如:
let date = new Date(2013, 0, 32); // 32 Jan 2013 ?!?
alert(date); // ...is 1st Feb 2013!
超出范围的日期组件是自动分发的。
假设我们需要将“2016年2月28日”的日期增加 2 天。在闰年的情况下,可能是“3月2日”或“3月1日”。我们不需要去想它,直接加。
let date = new Date(2016, 1, 28);
date.setDate(date.getDate() + 2);
alert( date ); // 1 Mar 2016
这个特性通常用于在给定的时间之后获得日期。举个例子,让我们来看看“未来 70 秒”的日期:
let date = new Date();
date.setSeconds(date.getSeconds() + 70);
alert( date ); // shows the correct date
我们也可以设置零甚至负的值,例如:
let date = new Date(2016, 0, 2); // 2 Jan 2016
date.setDate(1); // set day 1 of month
alert( date );
date.setDate(0); // min day is 1, so the last day of the previous month is assumed
alert( date ); // 31 Dec 2015
日期转数值,日期差值
把一个 Date 对象转换成数值的时候,相当于调用了 date.getTime() 函数,会得到当前时间的时间戳表示。
let date = new Date();
alert(+date); // 毫秒数, 等同于调用 date.getTime() 得到的值
重要的副作用是:日期可以被减去,结果是它们的时间戳之差。
这可以用于时间测量:
let start = new Date(); // start counting
// 搞些事情
for (let i = 0; i < 100000; i++) {
let doSomething = i * i * i;
}
let end = new Date(); // done
alert( `The loop took ${end - start} ms` );
Date.now()
如果我们只想测量时差,不需要创建 Date 对象。
有一个特殊方法 Date.now() 返回当期时间的时间戳形式。
在语义上等于 new Date().getTime(),但它不会创建一个中间日期对象。所以它更快,也不会给垃圾收集器带来压力。
使用它多是为了便捷或者性能考虑,像是 JavaScript 的游戏或者是其他类型的应用等。
所以这可能更好:
let start = Date.now(); // milliseconds count from 1 Jan 1970
// do the job
for (let i = 0; i < 100000; i++) {
let doSomething = i * i * i;
}
let end = Date.now(); // done
alert( `The loop took ${end - start} ms` ); // subtract numbers, not dates
标记
如果我们想要一个可靠的计算 CPU 消耗函数,我们应该小心。
例如,让我们来写一个计算两个日期之间时差的函数:哪一个更快?
// we have date1 and date2, which function faster returns their difference in ms?
function diffSubtract(date1, date2) {
return date2 - date1;
}
// 或者
function diffGetTime(date1, date2) {
return date2.getTime() - date1.getTime();
}
这两种方法完全相同,但是其中一个使用显式的 date.gettime() 来获取日期的时间戳标识,而另一个则依赖于一个日期到数字转换规则,他们的结果总是一样的。
那么,哪个更快呢?
第一个想法可能是连续运行很多次,并测量时间差异。对于我们的例子,函数很简单,所以我们要做大约10万次。
我们试试:
function diffSubtract(date1, date2) {
return date2 - date1;
}
function diffGetTime(date1, date2) {
return date2.getTime() - date1.getTime();
}
function bench(f) {
let date1 = new Date(0);
let date2 = new Date();
let start = Date.now();
for (let i = 0; i < 100000; i++) f(date1, date2);
return Date.now() - start;
}
alert( 'Time of diffSubtract: ' + bench(diffSubtract) + 'ms' );
alert( 'Time of diffGetTime: ' + bench(diffGetTime) + 'ms' );
哇!使用 getTime() 要快得多!这是因为没有类型转换,所以引擎更容易优化。
好的,我们有测量方式了,但这还不是一个很好的基准。
想象一下,在运行 bench(diffSubtract) 的时候,CPU 正在并行地做一些事情,它正在获取资源。在运行 bench(diffGetTime) 的时候,工作已经完成了。
对于现代多进程操作系统来说,这是一个相当真实的场景。
因此,第一个基准测试的 CPU 资源将少于第二个基准。这可能会导致错误的结果。
对于更可靠的基准测试,整个包的基准测试应该多次重新运行。
这里是代码示例:
function diffSubtract(date1, date2) {
return date2 - date1;
}
function diffGetTime(date1, date2) {
return date2.getTime() - date1.getTime();
}
function bench(f) {
let date1 = new Date(0);
let date2 = new Date();
let start = Date.now();
for (let i = 0; i < 100000; i++) f(date1, date2);
return Date.now() - start;
}
let time1 = 0;
let time2 = 0;
// run bench(upperSlice) and bench(upperLoop) each 10 times alternating
for (let i = 0; i < 10; i++) {
time1 += bench(diffSubtract);
time2 += bench(diffGetTime);
}
alert( 'Total time for diffSubtract: ' + time1 );
alert( 'Total time for diffGetTime: ' + time2 );
现代的JavaScript引擎开始将高级的优化应用于“热代码”,它执行很多次(不需要优化很少执行的东西)。因此,在上面的例子中,第一次执行没有经过充分的优化。我们可能想要增加一个热的运行:
// added for "heating up" prior to the main loop
bench(diffSubtract);
bench(diffGetTime);
// now benchmark
for (let i = 0; i < 10; i++) {
time1 += bench(diffSubtract);
time2 += bench(diffGetTime);
}
⚠️做基准测试时请小心
现代的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。
例如:
let ms = Date.parse('2012-01-26T13:51:50.417-07:00');
alert(ms); // 1327611110417 (timestamp)
我们可以从时间戳中立即创建一个 new Date() 对象:
let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') );
alert(date);
总结
在 JavaScript 中,日期和时间使用 Date 对象表示。我们不能创建“只包含日期”或“只包含时间”的对象:Date 对象总是携带这两部分。
月份是从 0 开始表示的(是的,就是指 1 月)。
周几是使用 getDay() 获取的,也是从 0 开始(表示周日)。
当为 Date 对象设置了超出正常范围的参数时,会自动修正。对应加/减日期/月份/小时非常有用。
日期对象可以用来相减,得出的是两个日期之间的时差(用毫秒表示)。这是因为日期对象会发生到数值(也就是时间戳)的转换。
使用 Date.now() 快速获得当前时间的时间戳表示。
注意,与许多其他系统不同的是,JavaScript 中的时间戳是以毫秒为单位的,而不是使用秒。
而且,有时我们需要更精确的时间测量。JavaScript 本身并没有一种方法来测量微秒(一百万分之一秒)的时间,但是大多数环境都提供了它。例如,浏览器有 performance.now() 方法用来测试从页面加载开始的毫秒数和微秒级的精度(点之后 3 位):
alert(`Loading started ${performance.now()}ms ago`);
// Something like: "Loading started 34731.26000000001ms ago"
// .26 is microseconds (260 microseconds)
// more than 3 digits after the decimal point are precision errors, but only the first 3 are correct
Node.js 有 microtime 模块和其他方法。从技术上讲,任何设备和环境都可以获得更精确的精确度,不只是用 Date 表示。
(完)