HN 日本語サマリー

← 一覧へ戻る
プログラミング

0年の2月のグリッチ

A glitch in February of the year 0 (28times.com)

6 pointsby lukasgelbmann0 コメント

要約

28times社が過去のタイムスタンプに対応する際、0000年2月の特定の日付でタイムスタンプが誤って処理されるバグを発見しました。調査の結果、PHPのDateTimeImmutableクラスでUnixタイムスタンプをDateTimeImmutableオブジェクトに変換する際、特定の記述方法において1日分のずれが生じることが判明。この問題はPHPの内部ライブラリtimelibに起因しており、筆者は修正を提案し、現在はワークアラウンドで対応しています。

全文翻訳

ブログ > 投稿 0年の2月のグリッチ Lukas Gelbmann 著、2026年6月26日 我々が見つけて修正した、稀な正確性の問題に関する技術レポート。 最近、遠い過去のタイムスタンプのサポートを追加していたとき、テスト中にチームメンバーが一部のタイムスタンプが正しく処理されないことに気づきました。この問題は、タイムスタンプ「0000-02-03 04:00 Europe/Oslo」で簡単に再現できました。最初の調査で、この問題はすべてのタイムゾーンで発生するものの、0年の2月(および1月の最後の数日間)に限られることが分かりました。ほとんどの時系列データは、2千年も前のタイムスタンプを持つことはありません。しかし、もちろん、サポートされている範囲のすべてのタイムスタンプを、希少な古代のケースであっても正しく解析したいと考えています。バグハントの時間です。 バグを探し始め、間違いなく自社のコードにあるだろうと仮定しました。我々はPHPランタイムが提供するカレンダーロジック(DateTimeImmutableクラス)を使用していますが、タイムスタンプには依然としていくつかの複雑な処理があります。タイムゾーンの移行によって曖昧になるタイムスタンプを扱うために、内部でUnixタイムスタンプを計算し、それをPHPのDateTimeImmutableに変換しています。0年は2つの点で少し異例です。まず、歴史家が使う伝統的なユリウス暦には存在しません(彼らはそれを紀元前1年と呼びます)。次に、天文年数表記を用いたプロレプティックグレゴリオ暦(28timesで採用しているカレンダー)では、0年は世紀の閏年です。世紀の閏年は例外中の例外です。つまり、100で割り切れる年は閏年ではないが、0年のように400で割り切れる場合は閏年となります。これが、0年がバグの影響を受けるかもしれないという漠然とした考えを与えました。しかし、他の世紀の閏年(2000年など)は影響を受けなかったため、これだけでは完全な説明にはなりませんでした。そこで、我々はコードをステップバイステップで確認し、問題を発見しました。驚いたことに、それは我々のコードにはありませんでした。問題は、UnixタイムスタンプをDateTimeImmutableオブジェクトに変換するために使用していたイディオムに関連していました。 根本原因 以下は、PHPでUnixタイムスタンプをDateTimeImmutableに変換する3つの方法です。 ```php \DateTimeImmutable::createFromFormat('U', '-62164356180') (new \DateTimeImmutable('@0'))->setTimestamp(-62164356180) new \DateTimeImmutable('@-62164356180') // incorrect return value ``` これら3つは完全に同等であるはずです。そして、ほとんどのタイムスタンプではそうなっています。しかし、最初の2つとは異なり、最後のバリアントは0年の2月に対して1日ずれた結果を返します。この記事執筆時点では、これは最近のすべてのPHPリリースで発生します。偶然にも、最後のメソッドが我々のコードで使用されていたものです。(これらの3つのスニペットでDateTimeをDateTimeImmutableに置き換えることもできます。DateTimeも最後のバリアントで同じ問題を抱えています。) 問題の修正 我々自身の目的のためには、最初の2つのメソッドのいずれかを使用するだけで修正できました。これは、DateTimeまたはDateTimeImmutableを使用している他のPHPプログラマにもお勧めしたい方法です。私はまた、日付/時刻機能を提供するライブラリtimelibのバグを修正するためのプルリクエストもオープンしました。PHPのDateTimeImmutableはこのライブラリを内部的に使用しています。問題は結局、timelibにプロレプティックグレゴリオ暦でUnixタイムスタンプを日付に変換するための2つの実装があることでした。そのうちの1つは、間違った日付(世紀の閏日の後ではなく、約1ヶ月前の0年1月に当たる日付)を使用する範囲チェックを持っています。これにより、世紀の閏日より前のすべての結果が1日ずれてしまいます。私は、すべての呼び出し元が正しいアルゴリズムを使用するように修正することを提案しました。この問題は、timelibとPHPの今後のリリースで修正されることを願っています。これは確かに満足のいくバグ修正となりました。クリーンなワークアラウンドがあり、timelibとPHPを改善できたのは素晴らしいボーナスです。