公元零年二月的一个错误

Hacker News Top 新闻

摘要

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

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/29 05:01

# 公元0年2月的一个小故障 来源:https://28times.com/blog/2026-06-26-february-of-the-year-0 28times图标28times标志 (https://28times.com/)博客 (https://28times.com/blog)\> 文章 一份关于我们发现并修复的罕见正确性问题技术报告。 最近,在添加对远古时间戳的支持时,一位团队成员在测试中发现某些时间戳未能正确处理。通过时间戳 `0000-02-03 04:00 Europe/Oslo` 可以轻松复现该问题。 初步调查表明,该问题影响所有时区,但仅出现在公元0年2月(以及1月的最后几天)。 大多数时间序列不会包含两千年前的时间戳。但当然,我们希望正确解析支持范围内的所有时间戳,即使是那些可追溯到古代的罕见情况。 ## 追查 Bug 的时刻 我们开始寻找这个Bug,并理所当然地认为它一定出在我们自己的代码中。我们使用了PHP运行时提供的日历逻辑(`DateTimeImmutable`类),但在处理时间戳时仍有一些非平凡的操作。为了处理因时区转换而产生歧义的时间戳,我们内部计算了Unix时间戳,然后将其转换为PHP的`DateTimeImmutable`对象。 公元0年在两个方面有些特殊。首先,历史学家使用的传统儒略历中并不存在“公元0年”(他们会称其为公元前1年)。其次,在采用天文年份编号的投影公历(即我们在28times上使用的日历)中,公元0年是一个世纪闰年(https://en.wikipedia.org/wiki/Century_leap_year)。世纪闰年是例外中的例外:能被100整除的年份不是闰年,除非——像公元0年那样——它们也能被400整除。 这让我们隐约明白了为什么公元0年可能受到Bug影响。但由于其他世纪闰年(如公元2000年)并未受影响,这不能作为完整解释。 于是我们逐步排查代码,最终找到了问题所在。令人惊讶的是,问题并不在我们的代码中。它与我们将Unix时间戳转换为`DateTimeImmutable`对象所用的惯用方法有关。 ## 根本原因 下面是在PHP中将Unix时间戳转换为`DateTimeImmutable`对象的三种方式。 1. `\\DateTimeImmutable::createFromFormat\('U', '\-62164356180'\)` 2. `\(new \\DateTimeImmutable\('@0'\)\)\-\>setTimestamp\(\-62164356180\)` 3. `new \\DateTimeImmutable\('@\-62164356180'\) //返回错误结果` 这三种方式本应完全等价——对于大多数时间戳也确实如此。但与前面两种不同,最后一种变体在公元0年2月的结果会偏差一天。在撰写本文时,所有最新的PHP版本都出现此问题。不巧的是,我们在代码中恰好使用了最后一种方法。 (你也可以将上述三种片段中的 `DateTimeImmutable` 替换为 `DateTime`。`DateTime` 在最后一种变体上存在同样的问题。) ## 修复问题 就我们自身而言,修复方法很简单:改用前两种方式之一。这也是我向其他使用 `DateTime` 或 `DateTimeImmutable` 的PHP程序员推荐的做法。 我还提交了一个拉取请求(https://github.com/derickr/timelib/pull/173)来修复 `timelib` 库中的这个Bug,该库提供了日期/时间功能。PHP的 `DateTimeImmutable` 内部使用了这个库。 问题的根源在于,`timelib` 有两个将Unix时间戳转换为投影公历日期的实现。其中一个实现中的范围检查使用了错误的日期——该日期落在公元0年1月,大约在世纪闰日之前一个月,而不是之后。这导致所有世纪闰日之前的结果偏差一天。我建议通过让所有调用者使用正确的算法来修复它。 这个问题有望在即将发布的 `timelib` 和 PHP 版本中得到修复。这无疑是一次令人满意的Bug修复——我们有了一个干净的变通方法,而且改进 `timelib` 和 PHP 也是一个不错的额外收获。

相似文章

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.

PHP 的古怪特性

Hacker News Top

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

重访天数计算

Lobsters Hottest

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

Y2K

Reddit r/singularity

一篇讨论Y2K bug及其历史意义的文章。