「日時はUTCで保存すれば安全」は、どこまで正しいのか
「日時はUTCで保存する」というのは、多くのエンジニアが実務の中で身につけてきた原則です。実際、この原則は多くの場面で有効に機能します。しかし、予約システムや繰り返し配信の実装で、この原則だけに頼って設計したところ、夏時間の切り替え時期に時刻がずれる不具合に遭遇した——という経験を持つ開発者もいるのではないでしょうか。
この記事では、UTC保存という原則が「何を守り、何を守らないのか」を、日時のフォーマットを定めるRFC 9557(RFC 3339の更新版)に基づいて整理します。
30秒で要点
- UTC保存が確実に守るのは「いつ起きたか」という過去の記録。「いつ起こすか」という未来の予約や繰り返し処理までは保証しない
- RFC 9557はRFC 3339を更新し、
Zの意味を「UTCでの時刻は分かるが、ローカル時刻へのオフセットは不明」に変更した - RFC 9557は、UTCオフセットをタイムゾーン名の代わりに使うことをMUST NOTで禁じている。オフセットは夏時間の切り替えルールを持たないため
- IANAのタイムゾーン名の命名規則自体が時代とともに変化しており、旧名称が現行版と一致しないことがある
なぜ「UTC保存で十分」と思い込みやすいのか
「日時をUTCで保存する」という原則は、「ローカル時刻のまま保存すると、どのタイムゾーンの時刻か分からなくなる」という典型的な失敗を防ぐために広まりました。この原則を守るだけで多くの不具合が防げるため、「UTCで保存しておけば、タイムゾーンの問題はもう考えなくていい」という結論に飛躍しやすくなります。
しかし、UTC保存が答えているのは「この出来事は世界共通の基準でいつだったか」という、過去の記録に関する問いです。「来年の同じ日の同じ時刻に処理を実行してほしい」という未来の予約や、「毎週月曜9時に配信する」という繰り返しの規則は、性質が異なる問いです。この2種類の問いを区別せずに同じ保存方法で扱おうとすることが、混乱の出発点になります。
「瞬間」と「規則」——2種類の日時
日時の情報は、大きく分けて2つの種類に分類できます。
- 瞬間を指す日時:「2026年9月18日10時00分(UTC)にこのイベントが発生した」というような、確定した1点の時刻の記録
- 規則を指す日時:「毎年9月18日の現地時間9時に処理を実行する」というような、将来のある地点でのローカルな時刻を指し示す約束事
UTCで保存する方式が確実に守るのは前者です。すでに起きた出来事は、UTCという単一の基準に変換して保存しておけば、後からどのタイムゾーンで見ても矛盾なく再現できます。
一方、後者の「規則」は、UTCへの変換だけでは表現しきれません。ある地点の「現地時間9時」が、UTCでいうと何時になるかは、その地点の夏時間ルールが変わるたびに変化する可能性があるためです。1年後の同じ日に実行したい処理を、今日時点のUTCオフセットで固定してしまうと、その地点で夏時間の切り替えがあった場合に、意図した現地時刻からずれて実行されることになります。
RFC 9557が明確にしたこと:オフセットとタイムゾーン名の違い
RFC 9557(2024年4月発行、Standards Track)は、日時のフォーマットを定めるRFC 3339を更新し、タイムゾーン名の情報をタイムスタンプに含められるようにした仕様です。この更新にあたり、RFC 9557はオフセットとタイムゾーン名の役割の違いを明確にしています。同仕様は、タイムスタンプに付随するUTCオフセットを、タイムゾーン名の代わりとして別のプログラムに渡すことを、MUST NOT(してはならない)として明示的に禁じています。理由は、そうするとその地点のUTCオフセットが不変であると不当に主張することになり、日時の加減算で誤った結果を生むためです。
RFC 9557が挙げる例はこうです。2020-01-01T00:00+01:00[Europe/Paris](タイムゾーン名付き)に6か月を加算すれば、Europe/Parisの夏時間ルールに従って正しく調整された結果が得られます。しかし2020-01-01T00:00+01:00[+01:00](オフセットをそのままタイムゾーン名の位置に入れたもの)に同じ加算をすると、Europe/Parisでは実際には1時間ずれた誤った結果になります。
確かなこと:RFC 9557が、UTCオフセットをタイムゾーン名の代わりに使うことを明示的に禁止していること、その理由が日時の加減算における誤りの防止であることは、条文と例示から直接確認できます。
まだ確かではないこと:この記事は「未来の予約時刻をどう設計すべきか」という一般的な設計論を扱っていますが、これはRFC 9557そのものの規定内容ではなく、上記の事実から導いた実務上の判断軸です。RFC自体が扱う範囲(Scope)にどこまでの設計指針が含まれるかは、実装時に一次情報で個別に確認することをおすすめします。
ZとUTCオフセットの意味も変わった
RFC 9557はもう1つ、Zという表記の意味も更新しています。旧仕様のRFC 3339(2002年7月発行、Klyne & Newman)は、Zと+00:00をいずれも「UTCが優先的な参照点であることを示す」ものとして、実質的に同じ意味で扱っていました。
これに対しRFC 9557は、Zの意味を「UTCでの時刻は分かるが、ローカル時刻へのオフセットは不明」という意味に変更しました。一方+00:00は、UTCが優先参照点であるという従来の含意を保持したままです。つまり、これまで同義とされていた2つの表記が、現在の仕様では異なるニュアンスを持つようになっています。既存システムで日時のパース処理を書いている場合、使用しているライブラリがRFC 9557に対応した解釈をしているか、旧来のRFC 3339のままの解釈をしているかによって、Zの扱いが変わる可能性があります。
タイムゾーン名そのものも変化する
「オフセットではなくタイムゾーン名を使えばよい」という結論に達したとしても、もう1つ押さえておくべき前提があります。IANA Time Zone Database(tzdb)の命名規則は、時代によって変化してきたという事実です。
IANAのtzdbドキュメントによれば、タイムゾーン名の各構成要素(スラッシュで区切られた部分)は14文字を超えてはならないという規則があり、こうした命名ガイドライン自体が時代とともに変化してきたとされています。同ドキュメントは、旧版の規則に従った名前が現行版の規則と一致しないことがある例として、US/Easternから___2020-01-01T00:00+01:00[Europe/Paris]0___への変更を挙げています。
古いシステムやデータベースに、こうした旧式のタイムゾーン名がそのまま残っている場合、現行の命名規則に対応したライブラリで正しく解釈できるかどうかを確認する必要があります。
判断軸:「UTCかローカルか」ではなく「瞬間か規則か」
ここまでの内容を踏まえると、タイムゾーン設計で最初に立てるべき問いが見えてきます。それは「UTCで保存すべきか、ローカル時刻で保存すべきか」ではなく、「この日時は瞬間を指しているか、規則を指しているか」です。
- 瞬間を指す日時(注文完了時刻、ログの記録時刻など):UTCで保存する原則がそのまま有効
- 規則を指す日時(毎週の配信予約、来年の同時刻に実行する処理など):UTCオフセットだけでなく、対象地点のタイムゾーン名を保持し、実行の都度、そのタイムゾーンの現在のルールに基づいて実際の時刻を計算する設計が必要
この区別をせずに「とりあえず全部UTCで保存する」という運用に統一すると、規則を指す日時までUTCオフセットに固定してしまい、夏時間ルールの変更時に静かに時刻がずれるという事故につながります。
自分でも試せる、最小の検証
自社のデータベースにある日時カラムを1つ選び、「その値に1年を加算したら、意図した現地時刻と一致するか」を実際に計算してみてください。UTCオフセットだけを保持しているカラムであれば、夏時間の切り替えがある地域(欧米の多くの地域など)を対象にした場合に、ずれが生じる可能性があります。タイムゾーン名を保持しているカラムであれば、ライブラリが現行の切り替えルールに基づいて正しく計算し直してくれるかを確認できます。
判断の土台として押さえておくこと
- UTC保存が守るのは「いつ起きたか」という過去の記録。「いつ起こすか」という未来の規則までは守らない
- 判断の軸は「UTCかローカルか」ではなく「瞬間を指すか、規則を指すか」
- UTCオフセットをタイムゾーン名の代わりに使ってはならない。夏時間の切り替えルールを含まないため、日時の加減算で誤差が生じる
- IANAのタイムゾーン名も命名規則の変化とともに更新されることがある。古い名称が残っていないか確認する
自社システムの日時設計を含め、API(システム間でデータをやり取りする窓口)・データ設計の前提を見直したい場合は、状況を整理するところから始めることができます。
状況を整理する(15分)より深く学ぶ
- API設計のベストプラクティス:RESTful、GraphQL、gRPCの選定基準と実践:API全体の設計判断を整理した記事
参考資料・引用元
- RFC Editor, "RFC 9557: Date and Time on the Internet: Timestamps with Additional Information". https://www.rfc-editor.org/rfc/rfc9557.html
- RFC Editor, "RFC 3339: Date and Time on the Internet: Timestamps". https://www.rfc-editor.org/rfc/rfc3339
- IANA, "Theory and pragmatics of the tz code and data". https://data.iana.org/time-zones/theory.html