什么是 Date::ITALY?
摘要
对 Ruby 的 Date::ITALY 常量的探讨,解释其与格里高利历改革和儒略日数的联系。
暂无内容
查看缓存全文
缓存时间: 2026/05/18 18:57
# 什么是 Date::ITALY?来源:http://aesthetikx.info/blog/date_italy.html
前几天我在查看 Ruby 的 backtrace 时,突然被某个东西吸引住了,停下了脚步:`Date::ITALY`。那会是什么呢?
`` irb(main):001> Date::ITALY => 2299161 ``
好吧,这可不是什么一目了然的东西。当我查阅 Date 类的文档时,又遇到了一些听起来不寻常的常量,包括 `ENGLAND`、`GREGORIAN` 和 `JULIAN`。文档对这些常量是怎么说的呢?
ENGLAND *英格兰及其殖民地的历法改革日的儒略日数。*
GREGORIAN *推定格里高利历的历法改革日的儒略日数。*
ITALY *意大利及一些天主教国家的历法改革日的儒略日数。*
JULIAN *推定儒略历的历法改革日的儒略日数。*
首先,我觉得 Ruby 源代码中提及*英格兰及其殖民地*以及*一些天主教国家*是挺幽默的。其次,我学到了一个新词*“proleptic”*(推定的,https://en.wiktionary.org/wiki/proleptic),它指的是将历法日期外推到其采用或使用时期之外。但好吧,大家真正关心的问题是——儒略日数到底是什么?别急,因为我们得先聊聊天主教的部分。
## 格里高利历的切换
由同名的*凯撒*(https://en.wikipedia.org/wiki/Julius_Caesar)确立的*儒略历*(https://en.wikipedia.org/wiki/Julian_calendar),在很长一段时间里都运行得相当准确。但并非完美,大约每 128 年就会少一天。到了 16 世纪 80 年代*教皇格里高利十三世*(https://en.wikipedia.org/wiki/Pope_Gregory_XIII)在位时,复活节的日期已经不可接受地向夏天移动了大约两周。1582 年,教皇诏书*《Inter gravissimas》*(https://en.wikipedia.org/wiki/Inter_gravissimas)宣布,1582 年 10 月 4 日星期四之后的一天将是 1582 年 10 月 15 日星期五。确实,Ruby 也遵循这一点,如果你试图实例化这十天期间内的日期,就会抛出异常:
`` irb(main):001> Date.new(1582, 10, 5) (irb):1:in 'Date#initialize': invalid date (Date::Error) ``
并且在迭代时会友善地跳过它们,好像什么都没发生过一样:
`` irb(main):001> Date.new(1582, 10, 4).next_day => #<Date: 1582-10-15 ...> ``
等等,我们在哪里见过这个数字?没错,`Date::ITALY` 编码了意大利从儒略历切换到格里高利历那一天的儒略日——儒略日 2,299,161。如果你曾经好奇 `Date#inspect` 中那些奇怪数字是什么,它们就是*儒略日数*(https://en.wikipedia.org/wiki/Julian_day)。
## 儒略日
像所有好故事一样,这个故事开始于六千多年前。在一个非常特别的星期一,公元前 4713 年的元旦,正午 UTC 整,人类最重要的三个多年周期完美同步:28 年*太阳周期*(https://en.wikipedia.org/wiki/Solar_cycle_(calendar)),19 年*太阴周期*(https://en.wikipedia.org/wiki/Metonic_cycle),当然还有 15 年的*罗马征税周期*,通常被称为*小纪*(indiction,https://en.wikipedia.org/wiki/Indiction)周期。实际上,儒略日周期是 28 × 19 × 15 = 7980 年,将在公元 3268 年再次轮回。
我们为什么需要它?正如我们之前看到的,人类历史中有一些“缺口”没有计入天数。更糟糕的是,并非所有国家都在同一时间采用格里高利历——例如英格兰一直等到 1752 年,因为新教徒不愿接受天主教的创新(因此有了 `Date::ENGLAND`)。因此,需要一种“通用中介”来在各种历法系统之间明确地来回计算。于是,从一个相当随意纪元开始简单计算天数的方法应运而生。就在格里高利历推行一年后,学者*约瑟夫·斯卡利杰*(https://en.wikipedia.org/wiki/Joseph_Justus_Scaliger)提出了儒略周期方法。令人难以置信的是,这个周期的名称并非源于儒略历或命名它的尤利乌斯·凯撒,而是以其父亲*尤利乌斯·凯撒·斯卡利杰*(https://en.wikipedia.org/wiki/Julius_Caesar_Scaliger)命名,而他父亲又是以尤利乌斯·凯撒命名的。这就像凯撒沙拉,以*凯撒·卡迪尼*(https://en.wikipedia.org/wiki/Caesar_Cardini)命名(而卡迪尼又以尤利乌斯·凯撒命名,而凯撒的名字实际上众所周知是盖乌斯)。
好了,回到 Ruby。我们现在知道 `Date::ITALY` 和 `Date::ENGLAND` 定义了意大利和英格兰分别采用格里高利历那一天的儒略日数。剩下的是 `Date::GREGORIAN` 和 `Date::JULIAN`。我猜你绝对猜不到:
`` irb(main):001> Date::GREGORIAN => -Infinity irb(main):002> Date::JULIAN => Infinity ``
好吧,这可不是一目了然。要理解这一点,我们必须了解 `Date` 的初始化方法:
`` new(year = -4712, month = 1, mday = 1, start = Date::ITALY) ``
这里有两件事:首先,默认日期等价于 `Date.jd(0)`。如果你曾好奇为什么 `Date.new.to_s` 给出 `"-4712-01-01"`,现在你知道了。其次,可选的 `start` 参数是“格里高利历的起始点”,以儒略日表示。如果你在 18 世纪的英格兰做 SaaS 产品,你会把*艾萨克·牛顿爵士*(https://en.wikipedia.org/wiki/Isaac_Newton)的生日表示为 `Date.new(1642, 12, 25, Date::ENGLAND)`,而不是 `Date.new(1643, 1, 4)`。这也意味着 `Date.new(1582, 10, 5, Date::ENGLAND)` 不会像 `Date::ITALY` 那样抛出异常。用英语的说法,牛顿生日以 1642/12/25 表示被称为“旧式”,而以 1643/1/4 表示则被称为“新式”。这就是 Ruby 的 `date_core.c` 中经常提到 `os` 和 `ns` 的原因。
如果你总想使用儒略历,你可以把“格里高利切换”无限期地往后推。这就是为什么 `Date::JULIAN` 是 `Float::INFINITY`。传递 `Date::GREGORIAN`(`-Float::INFINITY`)意味着切换发生在“无限久以前”,换句话说,“我们一直都在使用格里高利历”。因为这些值被认为是儒略日,你也可以提供自己的任意值。例如,如果你想在俄罗斯(格里高利历于 1918 年 2 月 14 日采用)建模日期,你可以:
`` irb(main):001> Date.new(1918, 1, 31, 2421639).next_day => #<Date: 1918-02-14 ...> ``
上帝保佑我们千万别换成新的历法系统,因为一个简单的 `start` 参数到时就不够用了!
## 结尾思考
如果你好奇艾萨克·牛顿究竟是在圣诞节出生还是 1 月 4 日,那么这正是格里高利十三世想要解决的问题!因为儒略周期是另外三个周期的同步,所以它被称为*三循环系统*(tricyclic system)。小纪周期被选为第三个周期,可能是因为历史记录通常不是与日期挂钩,而是与小纪数字关联。与我上面所说的相反,关于儒略周期最初是指儒略历还是以约瑟夫的父亲命名,是有争议的。
从概念上讲,你可以把 `Date` 看作一个 `(儒略日, 格里高利切换点)` 元组,再加上基于这两个值计算结果的方法。在日期的 `inspect` 格式中,例如 `#<Date: 1582-10-15T00:00:00+00:00 (2299161.0,0,2299161)>`,你看到的是用儒略日数表示的日期,加上时间部分的秒和纳秒,然后是时区偏移,最后是所给的格里高利切换点的儒略日。`DateTime` 会使用 `0s, 0ns` 部分作为时间组件。
留给读者作为练习,`class Date` 中还有一些有趣的方法:
- `#gregorian_leap?`
- `#julian_leap?`
- `#jisx0301`
- `#rfc2822`
- `#rfc3339`
## 延伸阅读
- https://docs.ruby-lang.org/en/master/language/calendars_rdoc.html#argument-start
- https://github.com/ruby/ruby/blob/master/ext/date/date_core.c
- https://quasar.as.utexas.edu/BillInfo/JulianDatesG.html
- https://en.wikipedia.org/wiki/Julian_day
相似文章
datasette 1.0a29
Datasette 1.0a29 已发布,包含新的实用方法、对空表格的 UI 改进,以及在 Codex CLI 协助下修复的竞争条件等错误修复。
PHP 的古怪特性
一位开发者在使用了五年后反思 PHP 的古怪之处,重点介绍了其数组实现和类型系统的奇特之处。
一夜之间格式化2500万行代码库:rubyfmt的故事
Stripe工程师分享了他们使用rubyfmt在一夜之间格式化2500万行Ruby代码库的经验,重点介绍了开发者生产力的提升。
@julien_c: 我今天才知道,Claude Code 会在一个月后自动删除你的会话痕迹
Julien C. 发现,Claude Code 会在一个月后自动删除会话痕迹。
Claude Code 的 Latitude
Latitude 是一款专为监控 Claude Code 令牌使用量的工具,帮助开发者跟踪消耗情况并避免触及速率限制。