让我们认识一个新的内置对象:日期。它存储日期、时间,并提供日期/时间管理方法。
例如,我们可以用它来存储创建/修改时间,来测量时间,或者只是打印出当前日期。
创建
要创建一个新的日期
对象,请使用以下参数之一调用new Date()
new Date()
-
无参数 – 为当前日期和时间创建一个
日期
对象let now = new Date(); alert( now ); // shows current date/time
new Date(毫秒)
-
使用从 1970 年 1 月 1 日 UTC+0 开始经过的毫秒数(1/1000 秒)创建一个
Date
对象。// 0 means 01.01.1970 UTC+0 let Jan01_1970 = new Date(0); alert( Jan01_1970 ); // now add 24 hours, get 02.01.1970 UTC+0 let Jan02_1970 = new Date(24 * 3600 * 1000); alert( Jan02_1970 );
表示自 1970 年开始经过的毫秒数的整数称为时间戳。
它是日期的轻量级数字表示形式。我们始终可以使用
new Date(timestamp)
从时间戳创建日期,并使用date.getTime()
方法将现有的Date
对象转换为时间戳(见下文)。1970 年 1 月 1 日之前的时间戳为负数,例如
// 31 Dec 1969 let Dec31_1969 = new Date(-24 * 3600 * 1000); alert( Dec31_1969 );
new Date(datestring)
-
如果只有一个参数,并且它是一个字符串,那么它将被自动解析。算法与
Date.parse
使用的算法相同,我们稍后会介绍。let date = new Date("2017-01-26"); alert(date); // The time is not set, so it's assumed to be midnight GMT and // is adjusted according to the timezone the code is run in // So the result could be // Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time) // or // Wed Jan 25 2017 16:00:00 GMT-0800 (Pacific Standard Time)
new Date(year, month, date, hours, minutes, seconds, ms)
-
使用给定的组件在本地时区创建日期。只有前两个参数是必需的。
year
应有 4 位数字。为了兼容性,还接受 2 位数字并将其视为19xx
,例如,此处98
与1998
相同,但始终强烈建议使用 4 位数字。month
计数从0
(1 月)开始,到11
(12 月)结束。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); // the same, hours etc are 0 by default
最大精度为 1 毫秒(1/1000 秒)
let date = new Date(2011, 0, 1, 2, 3, 4, 567); 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()、getUTCMonth()、getUTCDay()。只需在 "get"
后插入 "UTC"
即可。
如果你的本地时区相对于 UTC 有偏移,那么下面的代码将显示不同的小时
// current date
let date = new Date();
// the hour in your current time zone
alert( date.getHours() );
// the hour in UTC+0 time zone (London time without daylight savings)
alert( date.getUTCHours() );
除了给定的方法外,还有两个特殊的方法没有 UTC 变体
- getTime()
-
返回日期的时间戳 - 自 1970 年 1 月 1 日 UTC+0 起经过的毫秒数。
- getTimezoneOffset()
-
返回 UTC 和本地时区之间的差值(以分钟为单位)
// if you are in timezone UTC-1, outputs 60 // if you are in timezone UTC+3, outputs -180 alert( new Date().getTimezoneOffset() );
设置日期组件
以下方法允许设置日期/时间组件
setFullYear(year, [month], [date])
setMonth(month, [date])
setDate(date)
setHours(hour, [min], [sec], [ms])
setMinutes(min, [sec], [ms])
setSeconds(sec, [ms])
setMilliseconds(ms)
setTime(milliseconds)
(通过自 1970 年 1 月 1 日 UTC 起的毫秒数设置整个日期)
除了 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 日”。我们不必考虑它。只需添加 2 天。Date
对象将完成其余的工作
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); // the number of milliseconds, same as date.getTime()
重要的副作用:日期可以相减,结果是它们以毫秒为单位的差值。
这可用于时间测量
let start = new Date(); // start measuring time
// do the job
for (let i = 0; i < 100000; i++) {
let doSomething = i * i * i;
}
let end = new Date(); // end measuring time
alert( `The loop took ${end - start} ms` );
Date.now()
如果我们只想测量时间,则不需要 Date
对象。
有一个特殊方法 Date.now()
,它返回当前时间戳。
它在语义上等同于 new Date().getTime()
,但它不会创建中间 Date
对象。因此,它更快并且不会给垃圾回收施加压力。
它主要用于方便或在性能至关重要的情况下,例如 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;
}
// or
function diffGetTime(date1, date2) {
return date2.getTime() - date1.getTime();
}
这两个函数执行完全相同的事情,但其中一个使用显式 date.getTime()
以毫秒为单位获取日期,而另一个则依赖于日期到数字的转换。它们的结果始终相同。
那么,哪一个更快?
第一个想法可能是连续多次运行它们并测量时间差。对于我们的情况,函数非常简单,因此我们必须至少执行 100000 次。
让我们测量
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(diffSubtract) and bench(diffGetTime) 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 的大量文章可以在 https://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)
的调用以给定格式解析字符串,并返回时间戳(从 1970 年 1 月 1 日 UTC+0 开始的毫秒数)。如果格式无效,则返回 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
对象始终同时包含两者。 - 月份从零开始计数(是的,一月是零月)。
getDay()
中的星期几也从零开始计数(即星期日)。- 当组件超出范围时,
Date
会自动更正。适合于增加/减少天数/月数/小时数。 - 可以对日期进行减法运算,得到它们以毫秒为单位的差值。这是因为将
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, only the first 3 are correct
Node.js 有 microtime
模块和其他方法。从技术上讲,几乎任何设备和环境都可以获得更高的精度,只是不在 Date
中。
评论
<code>
标记,对于多行,请将其包装在<pre>
标记中,对于 10 行以上的内容,请使用沙箱 (plnkr、jsbin、codepen…)