JavaScript 的Date的许多异常点

Javascript Date 很奇怪。 众所周知,Brendan Eich 在 10 天内编写了 Javascript 的第一个版本 – Date 函数本身也不例外。 它是基于最终在 Java 中被弃用的代码。

这意味着 Javascript 继承了一个 Date 函数,该函数在 Java 中被发现是错误的和有问题的,因此它充满了问题。 我们甚至可能自己也遇到过一些问题。 那么,大家可能想知道,“这有什么奇怪的?”。 让我们看看 Javascript 的 Date 构造函数的所有异常和常见缺陷,从而避免它们。

Javascript 实际上不支持日期

这听起来和直觉不符,因为主要的 Javascript 日期构造函数为 Date,但 Javascript 实际上并不支持日期。 Javascript 仅支持日期时间。 所有 Javascript 日期都是下面的 Unix 时间戳。 这意味着如果我们尝试创建一个日期,我们实际上是在创建一个日期时间。 所有未指定时间的 Javascript 日期默认为该给定日期的午夜。

let date = new Date(2011, 1, 22);
// 请注意,生成的日期附有时间:
// Tue Feb 22 2011 00:00:00 GMT+0000 (Greenwich Mean Time)

解析日期

如果我们知道月份从 0 开始,那么像我们上面所做的那样解析日期可以正常工作,但是解析日期字符串在不同浏览器之间会有很大差异。 强烈建议不要解析日期字符串。 在 ECMAScript 5 规范之前,从未定义过 Date 解析字符串日期的方式,并且不同的浏览器有许多历史问题,使其非常不可靠。

根据当前规范,只有符合 ISO-8601 标准的字符串才能被 Javascript 解析,任何其他日期都应该返回 NaN,即:

let parseMyDate = Date.parse('2022-03-21T11:00:01+00:00');

然而,事实并非如此。 许多浏览器允许在此格式之外进行日期解析。 这就是它有可能令人困惑的地方。 假设我们要解析标准 dd/mm/yyyy 格式的日期格式。 我们采用标准日期,并将其传递给 parse() 函数:

let myDate = new Date("5/1/2022");
console.log(myDate);

在所有现代浏览器中,它使用美国日期格式,即 mm/dd/yyyy – 这意味着它返回 5 月 1 日,而不是 1 月 5 日,从而导致意外结果。

解析日期默认为 UTC

假设我们有一个没有时间或时区与之关联的日期:

let myDate = Date.parse('01 Jan 1999');
console.log(myDate);

我们可能会认为这并没有什么令人困惑的地方——它代表了一个固定的时间日期。 然而:

  • 如果我们的时区是 UTC,这将返回 915148800000。
  • 如果我们的时区是 UTC+3:00,这将返回 915138000000,即多 3 小时。
  • 如果我们的时区是 UTC-5:00,这将返回 915166800000,即减少 5 小时。

因此,如果我们的时区在 UTC 以西,例如 -5:00,则 Javascript 会从 Unix 时间戳中减去 5 小时。 由于日子从午夜开始。

这意味着如果我们尝试将此时间戳与不同的时区一起使用,例如,在后端系统中,我们不会得到 1999 年 1 月 1 日,而是 1998 年 12 月 31 日! 所有这一切都是因为 Javascript 没有实现日期——每个日期都有一个与之关联的时间——在这种情况下是午夜。

Javascript 日期中的月份从 0 开始

如果我们想在 Javascript 中创建一个日期,我们可以解析代表年、月和日的数字。 例如,如果我们想为 2011 年 2 月 22 日创建一个日期,我们会这样写

let date = new Date(2011, 2, 22);

只是,这给了我们 2011 年 3 月 22 日星期二 00:00:00 GMT+0000(格林威治标准时间)。 这是因为 Javascript 中的月份从 0 开始计数,所以二月是 1,而不是 2:

let date = new Date(2011, 1, 22);

不正确的日期跳到下个月

假设我们不小心创建了一个错误的日期,例如 2022 年 2 月 31 日。我们错误地将其从数据库或 API 传递到日期函数中:

let date = new Date(2022, 1, 31);

我们可能认为这只会返回 Invalid Date 或 NaN,但我们错了。 Javascript 跳到 3 月 3 日! 由于 2011 年 2 月只有 28 天,而且还有 3 天,所以这些天被添加到月底。 换句话说,我们不能相信 Date 在所有不正确的日期上都返回错误。

字符串不解析为数字

最奇怪的行为是当我们不给 Javascript 解析整个字符串时。 例如:

let myDate = new Date("0");
console.log(myDate);

我们可能认为这将返回 0 年,或者可能是 unix 纪元,但它实际上返回 2000 年 – Sat Jan 01 2000 00:00:00 GMT+0000(格林威治标准时间)。

然而,更奇怪的是,如果我们试图增加它,它会在几个月内开始计算:

console.log(new Date("5")); // Tue May 01 2001 00:00:00 GMT+0100 (British Summer Time)
console.log(new Date("11")); // Thu Nov 01 2001 00:00:00 GMT+0000 (Greenwich Mean Time)
console.log(new Date("4")); // Sun Apr 01 2001 00:00:00 GMT+0100 (British Summer Time)

最重要的是,如果我们尝试执行 new Date("13") ,我们将得到 Invalid Date 作为结果,因为没有第 13 个月。

使用时间戳可能会混淆日期

如果我们只将一个数字传递给 new Date(),它会将其视为 Unix 时间戳 – 但是,它不会针对时区进行调整。 例如,在 UTC 中,以下代码返回 Thu Jan 01 1970 00:00:00 GMT+0000(格林威治标准时间)

console.log(new Date(0));

这是有道理的,因为它是 Unix 时代,如果特定时间对我们很重要,那可能没问题。 但是,如果我们在 UTC-5:00 中,该代码将返回不同的日期,即 1969 年 12 月 31 日星期三 19:00:00 GMT-0500(东部标准时间) – 即 5 小时前。 这意味着如果我们使用时间戳代替日期,因为 Javascript 不直接支持日期,当使用 Date().toLocaleString() 之类的方法时,我们会立即遇到问题。 最终,我们可以使用 .toUTCString() 方法解决这个问题——但这种复杂性并不完全明显。

年份不一致

你可能认为我们很轻松,只有时间戳和时区被破坏 – 但即使是年份也是不一致的。 如果我们想为 0 年的 1 月 1 日创建一个日期,你可能会认为我们会这样写:

console.log(new Date(0, 0, 0));

由于月份从 0 开始,这看起来是正确的——但实际上,如果年份小于 100,则 0 表示 1900 年。好吧,你可能会想,我想这应该返回 1900 年 1 月 1 日——但这实际上也是错误的——因为 天数从 1 开始索引,而不是 0。上面的代码返回 Sun Dec 31 1899 00:00:00 GMT+0000(格林威治标准时间) – 因为该月的第 0 天算作上个月的最后一天。 以下是一些其他示例:

console.log(new Date(0, 0, 0)); // Sun Dec 31 1899 00:00:00 GMT+0000 (Greenwich Mean Time)
console.log(new Date(50, 0, 0)); // Sat Dec 31 1949 00:00:00 GMT+0000 (Greenwich Mean Time)
console.log(new Date(30, 0, 0)); // Tue Dec 31 1929 00:00:00 GMT+0000 (Greenwich Mean Time)
console.log(new Date(24, 0, 0)); // Mon Dec 31 1923 00:00:00 GMT+0000 (Greenwich Mean Time)

一旦超过 100 年,它就会回到正常计算年份。 所以下面的代码实际上给了我们 101 年,而不是 2001 年:

console.log(new Date(101, 0, 0)); // Fri Dec 31 0100 00:00:00 GMT-0001 (Greenwich Mean Time)

如果我们使用的是 1900 年之后的几年,这可能会很有用,但对于之前的任何事情来说,这都是令人难以置信的违反直觉的。

为什么没有人修复 Javascript 日期?

Javascript Date 函数从根本上在许多方面都被破坏了——这就是为什么大多数人使用像 Moment.js 这样的工具,但为什么它没有得到修复?

它们没有得到修复的主要原因是因为大多数 Web 都是基于考虑到 Date 缺陷的代码构建的。 因此,现在更改将导致许多网站容易崩溃。

为了解决这种情况,Javascript 引入了一套全新的标准,称为 Temporal,它将占用与 Date 不同的命名空间,并将解决本文中描述的大部分问题。 在那之前,我们一直被 Javascript Dates 产生的异常所困扰。