什么是 Date::ITALY?

Hacker News Top 工具

摘要

对 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

相似文章

Dates That Don't Exist (2015)

Hacker News Top

A blog post exploring the nonexistent dates of October 5-14, 1582 due to the Gregorian calendar reform, and how Ruby, Python, and Perl handle these invalid dates.

重访天数计算

Lobsters Hottest

Tony Finch 重新审视了他的格里高利历到儒略历天数转换算法,融合了 Ben Joffe 的技巧,得出了一个更高效、更稳健的公式。

公元零年二月的一个错误

Hacker News Top

PHP 的 DateTimeImmutable 中一个罕见的错误导致公元零年二月出现日期偏差一天的问题;解决方法是使用替代的时间戳转换方法。

PHP 的古怪特性

Hacker News Top

一位开发者在使用了五年后反思 PHP 的古怪之处,重点介绍了其数组实现和类型系统的奇特之处。