Twitter 上看到一个 5 天前的主题贴,为什么 0 代表 1 月份?顿时勾起了我的好奇,曾经我也是有过这个疑问的人,这是我最接近答案的一次。

我看到的主题贴

没遇到过的话,可以打开浏览器的开发者工具,在控制台输入 new Date('2020-01-01').getMonth() 你得到的结果会是 0。

有个叫 Hillel is on vacation 的大佬在帖子下详细的描述了他探索这个问题答案的过程,从现代 C 语言的 time.h 开始,最早追溯到 1964 年的 Multics 分时操作系统源码,令人拍案叫绝。

整个过程简单地用流水账记一下:

首先,Javascript 这么做纯粹是因为借鉴了 C 语言。

在 The C Programming Language 一书中写到,time.h 里有个叫 tm 的结构体,里面除了 tm_mday(day of the month) 是从 1 开始计算的,其它都是从 0 开始。为什么它这么特殊呢?在它的背后一定有故事。

C 语言 time.h

在 The C Programming Language 第二版中有提到,time.h 来自 ANSI 标准 C89,关于 C89 有个有趣的事情,它需要兼容过去 17 年的所有不同版本的编译器,但是编译器似乎和问题的答案无关。

C89 time.h

time.h 遵循 POSIX 标准,该标准用于在各种 Unix 操作系统上运行软件,于是开始对比各种版本的 Unix。令人惊讶的是,几乎所有不同的 Unix 操作系统都使用了相同的 tm 结构体,说明这是一个非常非常古老的问题。

OSes time.h

有趣的是,1974 年的 Unix 5 根本就没有 time.h 而是 ctime.c, 从 Unix 7 开始才出现 tm 结构体。

Unix 5 的 day of the month 仍然是从 1 开始计数的。

Unix ctime.h

比 Unix 5 更早的版本已经没有记录了,到此毫无进展,想到 Unix 的灵感来源于 Multics,于是查阅 Multics 的源码,所有的时间都是从 1 开始计数的,看来和问题的答案无关。

Multics Source Code

回到 Unix 5,查看实现方式,终于有了大胆的猜测:当时的计算机内存只有几 KB,因此,程序员们需要在有限的资源内完成计算,为了优化月份和 day of the week 的复杂指针计算,就把它们从 0 开始计数,而其它的只是用于展示,所以就怎么方便怎么来,所以出现了 day of the month 从 1 开始计数。

Unix 5 asctime

流水账终于记完了。

虽然看不懂这些古老的指针,但却有一种拨开云雾见天明的感觉。

为了方便你们阅读,特地整理了相关术语表:

  • C89:1989 年,ANSI 发布了第一个完整的 C 语言标准 —— ANSI X3.159—1989,简称 “C89”,不过人们也习惯称其为 “ANSI C”。
  • ANSI:美国国家标准学会
  • POSIX:可移植操作系统接口(英语:Portable Operating System Interface,缩写为POSIX)是IEEE为要在各种 UNIX 操作系统上运行软件,而定义API的一系列互相关联的标准的总称。
  • Multics:Multics 是一个分时操作系统,该系统开始作为一个合资项目,是 1964 年由贝尔实验室、麻省理工学院及美国通用电气公司所共同参与研发的,其目的是为了开发出一套安装在大型主机上多人多工的操作系统。