タイムスタンプ、タイムゾーン、そして 2 回訪れる 1 時間

Unix 時間が実際に何を数えているのか、ISO 8601 のオフセットと IANA のタイムゾーンがどう違うのか、夏時間がアラームや所要時間に何をするのか、そして UTC で保存することが誤りになる場面を解説します。

タイムスタンプは 2 つの問いのうち一方に答えるものであり、日付まわりのバグの大半はこの 2 つの取り違えから生まれます。タイムスタンプが指すのは、すべての観測者が一致する時間軸上の一点である 瞬間 か、あるいは誰の時計かが分からなければ意味を持たない 壁時計の読み かのどちらかです。支払いはある瞬間に起きたものであり、歯科の予約は壁時計の読みです。一方を他方として保存すれば、後になって、日曜の朝にきっかり 1 時間ずれて破綻します。

Unix 時間は数えているだけで、時刻を告げてはいない

Unix 時間は、1970-01-01 00:00:00 UTC からの経過秒数です。ここでの UTC は、どこから数え始めたかを示しているだけであり、値そのものはタイムゾーンを持ちません。同じ瞬間は、アムステルダムでもオークランドでもリマでも同じ数値です。

問題になるのは単位なので、桁数を数えてください。

桁数単位
101756080000
131756080000000ミリ秒

現在の日付はいずれも、秒なら 10 桁、ミリ秒なら 13 桁になり、2286 年まではそうあり続けます。どちらの読み違いも派手に現れます。ミリ秒を秒として読めば 57626 年に、秒をミリ秒として読めば 1970 年 1 月に着地します。解釈結果の日付を表示するタイムスタンプコンバーターを使えば、一目で分かります。

もう 1 つの制約は入れ物です。符号付き 32 ビットの秒数は 2147483647 で終わります。これは 2038 年 1 月 19 日 03:14:07 UTC にあたり、そこから負に回り込んで 1901 年 12 月として読まれます。30 年後の有効期限を保存するようなものは、すでにその先を計算しています。符号なし 32 ビットなら 2106 年まで届きますが、1970 年より前の日付をすべて失います。64 ビットには実用上の上限がありません。

ISO 8601 と RFC 3339

覚えておく価値のある形は 2026-08-25T14:30:00Z です。日付、T、時刻、そしてゾーン指定子という並びです。RFC 3339 はインターネットプロトコル向けに ISO 8601 を厳しくしたプロファイルで、このゾーン指定子を必須としています。ISO 8601 自体は週日付や、20260825T143000Z のような区切りのない形、オフセットのないタイムスタンプも許しています。ですから「ISO 8601」をうたう API がたいてい意味しているのは、RFC 3339 の部分集合です。

Z はオフセットがゼロであることを意味します。+02:00 は、現地の時計が UTC より 2 時間 進んでいる ことを意味するので、UTC を求めるには引き算します。これを逆にするのが、よくある 2 時間ずれの間違いです。

文字列指しているもの
2026-08-25T14:30:00Z瞬間。曖昧さはない
2026-08-25T16:30:00+02:00同じ瞬間を、2 時間進んだ時計で表したもの
2026-08-25T14:30:00壁時計の読み。瞬間ではない
2026-08-25日付。長さは 23 時間、24 時間、25 時間のいずれか

Z もオフセットも付いていない文字列は曖昧であり、パーサーごとに解釈が異なります。UTC と仮定するものもあれば、システムのゾーンと仮定するものもあります。誕生日のような日付だけの値が、グリニッジより西では 1 日早く表示されがちなのは、これが理由です。

オフセットはタイムゾーンではない

+02:00 は、ある場所のある瞬間についての事実です。Europe/Amsterdam は規則の集合であり、冬は +01:00、夏は +02:00、さらに数十年前には別の形をしていました。オフセットを保存すると、測定した瞬間についての答えは残りますが、それ以外を計算する能力は失われます。

オフセットは一意でもありません。夏の午後の +02:00 は、アムステルダム、ラゴス、ヨハネスブルグを同時に指します。オフセットだけからは規則にたどり着く道がなく、その時計が次の 12 月に何時を指すかも分かりません。略称はさらに悪く、CST は 3 つの異なるゾーンを指します。IANA の識別子を Area/Location の形で保存してください。

存在しない 1 時間と、2 回訪れる 1 時間

EU 全域では、時計は 3 月の最終日曜に進み、10 月の最終日曜に戻ります。アムステルダムでは春に 02:00 が 03:00 になるため、その日は 02:00:00 から 02:59:59 までの現地時刻がすべて存在しません。秋には 03:00 が 02:00 になるため、02:30 は 2 回訪れます。1 回目は +02:00 で、2 回目はその 1 時間後に +01:00 で訪れます。

  • 繰り返しのアラーム。 現地時刻 02:30 に設定されたジョブは、春の日曜には妥当な時刻を持ちません。スケジューラーによって挙動は異なり、飛ばすもの、03:00 にずらすもの、01:30 にずらすものがあります。秋の日曜には、同じジョブが 1 回発火することも 2 回発火することもあるので、cron パーサーで次の数回の発火時刻を確認してください。
  • ログファイル。 オフセットのない現地時刻のタイムスタンプが 1 時間ぶん並んでいると並べ替えられませんし、1 時間離れた 2 つの出来事がまったく同じ文字列を持つことになります。ログは UTC で記録するか、各行にオフセットを付けてください。
  • 所要時間。 アムステルダムの現地時刻で、2026 年 10 月 24 日の 20:00 から翌日の 20:00 までは 25 時間です。「1 日足す」と「24 時間足す」は別の操作であり、暦日で数える日付の差は、時間単位で扱う所要時間コンバーターとは別の問いに答えています。

規則は変わるので、保存の仕方はそれに耐えなければならない

これらの規則を保持しているのが IANA のタイムゾーンデータベース tzdata で、年に数回公開されます。規則が法令だからです。各国の政府は夏時間を廃止したり導入したり、切り替え日を動かしたりします。ときには数週間前の通告で行われます。固定されたコンテナーイメージ、ブラウザー、言語のランタイムはそれぞれ自前の写しを持っており、1 台のマシンの中で食い違うこともあります。将来の現地時刻を瞬間に変換することは予測であり、その日が来て初めて確定します。

値の種類保存するもの理由
すでに起きた出来事UTC の瞬間過去は立法で変えられない
壁時計に基づく将来の予定現地の日時と IANA のゾーン ID意図は時計の読みにある
ゾーンをまたいで固定された将来の瞬間UTC意図は瞬間にある
時刻のない日付ゾーンも深夜 0 時も付けない素の日付ゾーンを付けるとずれが生じる

来年の会議に対して「とりあえず UTC で保存する」が誤りなのは、これが理由です。Europe/Amsterdam の 2027 年 3 月 30 日 09:00 を今日 UTC に変換すると、答えは 07:00Z になります。それまでに切り替え日が動けば、会議は依然として現地時刻 09:00 のはずなのに、07:00Z は 08:00 として表示されます。保存した値は予測を凍結し、意図を捨ててしまったのです。真実の源としては現地時刻とゾーンを保持してください。その隣にある UTC の列は、あくまでキャッシュです。

人に日付を見せる

03/04 は文字列だけからは判別できません。世界の多くでは 4 月 3 日、米国では 3 月 4 日です。03/04/05 になると未知数が 3 つになります。人が読む出力では月を語で綴り (4 Apr 2026)、機械どうしのやり取りには ISO を使ってください。Discord のタイムスタンプタグは、読んだ人のゾーンとロケールで表示することでチャット上のこの問題を回避します。曖昧さのない値を保存し、書式化は端で行うということです。

週番号には独自の規則があります。ISO の週は月曜に始まり、第 1 週は 1 月の最初の木曜を含む週、言い換えれば 1 月 4 日を含む週です。そのため 1 月 1 日は前の ISO 年の第 52 週や第 53 週に入ることがあり、12 月 29 日は翌年の第 1 週に入ることがあります。北米でよく使われる慣習では、代わりに 1 月 1 日を含む日曜始まりの週から数えるため、同じ日付に 2 つの番号が付きます。曜日計算ツールの ISO 週は、表計算ソフトと突き合わせて確認しておく価値があります。

所要時間、月、うるう年

「1 か月後」は固定の日数ではありません。28 日、29 日、30 日、31 日のいずれかです。1 月 31 日に 1 か月を足す操作に正しい答えはなく、ライブラリは一般に 2 月の末日に丸め込みます。その結果、この操作は可逆でなくなります。1 か月足してからもう一度引くと、1 月 31 日は 1 月 28 日になっています。丸めも同じ落とし穴の縮小版です。時刻を最も近い 15 分に切り捨てるのと、最も近い 15 分に丸めるのとでは、最大で 1 区間ぶん違います。

うるう年の規則を完全に述べるとこうなります。4 で割り切れる年はうるう年であるが、100 で割り切れる年は例外であり、ただし 400 でも割り切れる年は再びうるう年である。したがって 1900 年はうるう年ではなく、2000 年はうるう年であり、2100 年はうるう年になりません。平均すると 1 年は 365.2425 日です。4 で割り切れるかどうかだけを判定するコードは、今を生きる誰もが記憶している年についてはすべて正しいので、2100 年まで生き延びます。2 月 29 日の記念日は、これに関連する厄介ごとです。翌年には 2 月 28 日と 3 月 1 日という候補がありますが、どちらを選ぶかを決める規則はないので、意図して一方を選んでください。