【问题标题】:Why does the month argument range from 0 to 11 in JavaScript's Date constructor?为什么 JavaScript 的 Date 构造函数中的月份参数范围是 0 到 11?
【发布时间】:2011-02-02 21:27:51
【问题描述】:

当使用以下调用在 JavaScript 中初始化一个新的 Date 对象时,我发现月份参数从零开始计数。

new Date(2010, 3, 1);  // that's the 1st April 2010!

为什么月份参数从 0 开始?另一方面,月份参数(最后一个)是从 1 到 31 的数字。这样做有充分的理由吗?

【问题讨论】:

  • 只是为了让你保持警觉。
  • 一个也是零索引的是the Day of the week (integer) 0-6
  • 因为它是为机器而不是为人类编码的。但它仍然是一个巨大的错误来源,因为很多代码(仍然)是由人类编写的 :)
  • @Christophe 同样的论点也应该适用于日期和年份。
  • @ChristopheRoussy 是的,如果它是为机器编码的,那么为什么要从 1 开始索引天?

标签: javascript datetime date


【解决方案1】:

这个问题的真正答案是它是从java.util.Date 复制的,它也有这个怪癖。可以从最初实现 JavaScript 的 Brendan Eich 在 Twitter 上找到证明:

https://twitter.com/BrendanEich/status/481939099138654209

https://twitter.com/BrendanEich/status/771006397886533632

Brendan 还指出,是 Netscape 的 Ken Smith 从 Java 移植过来的。

https://twitter.com/BrendanEich/status/771006208949891072

这发生在 1995 年,JDK 1.0 处于测试阶段。它于 1996 年推出。1997 年,JDK 1.1 出现,它弃用了 java.util.Date 上的绝大多数功能,将它们转移到 java.util.Calendar,但即使这样仍然有从零开始的月份。受够了这一点的开发人员创建了 Joda-Time 库,最终导致 java.time 包被嵌入到 Java 8 (2014) 中。

简而言之,Java 花了 18 年的时间才获得了正确设计的内置日期/时间 API,但 JavaScript 仍停留在黑暗时代。我们确实有优秀的库,例如 Luxon Moment.jsdate-fnsjs-joda 等。但截至目前,语言中只内置了Date。希望这会在不久的将来通过the TC39 Temporal proposal 改变。

【讨论】:

  • 啊...很好的旧的演示驱动开发方法。
  • @ÁlvaroGonzález 我会责怪最初引入它的原始 JDK 1.0 开发人员。
  • @barell 为什么要怪罪于为 Java 的第一个版本建模的那个人,自第一个版本以来,没有一种语言可以解决所有问题。 Java 实际上更正了他们的 Date api,但 Javascript 仍然坚持旧的,javascript 开发人员至少应该给我们一个新的 api,就像 Java 引入 LocalDate 一样。
  • @Yassir - 这项工作正在进行中。 tc39.es/proposal-temporal/docs
  • 请注意最后一段:即使在更现代的图书馆中,也并非一切都是美好的。 Moment().month() 和 date-fn 的 getMonth() 仍然是从 0 开始的。 Luxon 和 js-joda 的月份函数似乎是从 1 开始的。
【解决方案2】:

这是编程世界中一个古老的(可能是不幸的,可能正在消亡的)传统,请参阅旧标准 (POSIX) localtime C 函数 http://linux.die.net/man/3/localtime

【讨论】:

  • JS Date 对象是从 Java 1.0 移植过来的,这就是原因。继承它的所有缺陷...stackoverflow.com/questions/344380/…
  • 你是对的,传统通常是完全不一致的,而且常常是不合理的,我真的希望这种“坏”的传统真的消亡......
  • 是 2019 年,我正在解决与此行为相关的问题,因此只要 angular 框架和 javascript 等语言不弃用它,它仍然会发生 - 请随时在 2025 年及以后发表评论;-)
  • 这是一个让我总是花时间调试日期问题的传统......想知道这个传统造成了多少浪费的工作时间。
  • 永远会改变。假设这种行为的现有代码数量之多令人费解。 (我的意思是,在 JavaScript 中。)
【解决方案3】:

除月份中的某天以外的所有内容都是从 0 开始的,see here for a full list including ranges :)

这实际上是基于 1 的天数,这是这里的怪事……真是够奇怪的。为什么这样做?我不知道...但可能发生在他们被贴满并决定分号是可选的同一次会议上。

【讨论】:

  • "ones-based" days 的事情可能是因为他们头脑正常的人不会为天创建一个字符串名称数组(例如,{ "first", "second", "third", ..., "twenty-seventh", ... })并尝试通过@987654323 对其进行索引@。再说一次,也许他们只是看到了使 off by one 错误成为经常发生的绝对实用性。
  • 他们为什么年份不是从 0 开始的?
  • @Vedmant(出于实际目的)在消极和积极的方向上都有无限的年数。年份根本不是真正的“基于”,因为它不是一组固定的值。
【解决方案4】:

2021 年真正的问题是:

如果Date 从 JS 的第一天就被破坏了,为什么 26 年后(如果从零开始算起 25)它仍然存在?

JS 有很多不好的部分/怪癖,最糟糕的是其中一些在 几十年 中都没有得到修复(有趣的事实:我们正在谈论 Date 日期)。

但我们很幸运,看起来Date 的结尾快到了。

剧透

new Temporal.PlainDate(2020, 11, 26); // => 2020-11-26

时间(提案)

来自提案介绍:

Date 一直是 ECMAScript 的痛点。这是针对 Temporal 的提议,这是一个充当顶级命名空间(如 Math)的全局 Object,它为 ECMAScript 语言带来了现代日期/时间 API。

根据文档中的报告,它基于“我们检查的常见用例”,并且针对不同类型的数据具有多种类型:

  • Plain 类型没有时区
  • ZonedDateTime 类型有一个时区
  • 大多数类型都有日历
  • 许多有用的类型:Absolute, DateTime, Date, Time, TimeZone, ...

这是一个概述各种Temporal 类型及其关系的架构

您可以在可用的documentation 和漂亮的cookbook 中阅读更多关于它们的信息。或者探索当前的issues 以获得开放的。

Temporal 何时可用

由于March '21,它处于第三阶段/草稿(参见TC39 stages reference)。请参阅 TC39 March '21 slides

现在它可以很快被包含在 TypeScript 中,因为他们的policy 是:

当新功能达到第 3 阶段时,它们就可以包含在 TypeScript 中了。

对于 Node 和主流浏览器,我们可能需要等待更多时间。

今天开始试验

与此同时,您可以使用Temporal polyfill 进行试验。

npm install --save proposal-temporal

// temporal-test.js
const { Temporal } = require('proposal-temporal');

// or as an ES module
// import { Temporal } from 'proposal-temporal/lib/index.mjs';

// 11 means November now!
const date = new Temporal.PlainDate(2020, 11, 26); // => 2020-11-26

const sameDate = Temporal.PlainDate.from(
                {year: 2020, month: 11, day: 26}); // => 2020-11-26

【讨论】:

    【解决方案5】:

    一年总有 12 个月,因此早期的 C 实现可能使用索引为 0..11 的静态固定宽度数组。

    【讨论】:

    【解决方案6】:

    我知道这并不是对原始问题的真正答案,但我只是想向您展示我对这个问题的首选解决方案,我似乎从来没有记住它,因为它不时弹出。

    小函数 zerofill 可以在需要的地方填充零,月份只是 +1 添加:

    function zerofill(i) {
        return (i < 10 ? '0' : '') + i;
    }
    
    function getDateString() {
        const date = new Date();
        const year = date.getFullYear();
        const month = zerofill(date.getMonth()+1);
        const day = zerofill(date.getDate());
        return year + '-' + month + '-' + day;
    }
    

    但是,是的,Date 有一个非常不直观的 API,当我阅读 Brendan Eich 的 Twitter 时,我笑了。

    【讨论】:

      【解决方案7】:

      在java中也是这样。可能将int转换为字符串(0 - jan,, 1-feb),他们这样编码..因为他们可能有一个字符串数组(从 0) 开始索引的月份名称和这些月份编号,如果它们从 0 开始,则映射到月份字符串会容易得多..

      【讨论】:

        【解决方案8】:

        他们可能认为月份是一个枚举(第一个索引为 0),而天则不是,因为它们没有与之关联的名称。

        或者更确切地说,他们认为一天的数字是一天的实际表示(就像月份在像 12/31 这样的日期中表示为数字一样),就好像你可以用数字作为变量进行枚举,但实际上是从 0 开始的。

        所以实际上,对于月份,也许他们认为正确的枚举表示是使用月份的名称,而不是数字,如果天有名称表示,他们也会这样做。想象一下,如果我们说 1 月 5 日、1 月 6 日,而不是 1 月 5 日、1 月 6 日等,那么他们可能也会进行几天的基于 0 的枚举......

        也许他们潜意识里想到了将月份枚举为 {January, February, ...} 以及将天数枚举为 {One, Two, Three, ...} 的天数,除了您将天数作为数字而不是名字,比如 1 代表 One 等等,所以不可能从 0 开始...

        【讨论】:

        • 你应该双修心理学家。这仍然是他们犯的错误,但至少我们现在明白他们为什么犯了。
        【解决方案9】:

        这可能是一个缺陷,但是当您想将月份或星期几表示为字符串时,它也非常方便,您可以创建一个像 ['jan,'feb' ...etc][new Date().getMonth()] 代替 ['','jan',feb ...etc][new Date().getMonth()] 或 ['jan','feb' ...etc][新日期().getMonth()-1]

        一个月中的日子通常没有命名,因此您不会为这些日子制作带有名称的数组。在这种情况下 1-31 更容易处理,所以你希望每次都减去 1...

        【讨论】:

        • 并非如此。您可以轻松地减去一个。它产生的问题多于解决的问题,因为现在当您使用日期进行数学运算时,您必须经常对月份进行特定的操作。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-13
        • 2010-10-15
        • 1970-01-01
        • 2022-12-07
        • 1970-01-01
        相关资源
        最近更新 更多