Skip to content
Published on

時間と日付の技術的地獄 完全解説 — TimeZone、DST、leap second、Temporal API、NTP 徹底ガイド (2025)

Authors

はじめに — 「2024 年 10 月 27 日 02:30」は存在しないかもしれない

ヨーロッパでサマータイムが終わる日、夜明けの 03:00 になると時計は 02:00 に巻き戻る。つまり同じ日に 02:30 が 2 回 現れる。その日の 02:30 にクーポンを発行したイベントがあったら、そのクーポンはどちらの 02:30 のものだろうか?

逆に、3 月最終日曜のヨーロッパは 02:00 から 03:00 に飛ぶ。02:30 という時刻そのものが 存在しない。ユーザーが「02:30 に予約」と保存したら、その予約はいつ実行されるのか?

これらの問いは単なる UX の問題ではない。金融取引の order ソート、ログの時系列分析、分散システムのイベント ordering、給与計算 のような現場のシステムがこの微妙さで壊れる。Jon Skeet(Noda Time の作者)の有名な言葉: 「Time is a joke. Seriously.」

本稿は、時間と日付を扱う技術的基盤をゼロから解説する。UTC とは何で GMT とどう違うか、Unix time はなぜ leap second を無視するのか、IANA tz データベースがなぜ頻繁に更新されるのか、DST 切り替えをどう安全に処理するか、new Date() の悪名高い落とし穴、JavaScript Temporal API がなぜ 10 年ぶりの革新なのか、分散システムで NTP/PTP により時計を合わせる方法、そして Google Spanner の TrueTime まで。

Unicode の記事で「文字列は難しい問題だ」と書いたなら、時間はそれより難しい。文字列は少なくとも 1 台のコンピュータ内では決定論的だが、時間は地球の自転速度の変化、国家の政治的決定、OS の clock drift まで絡んでいる。


1. 時間の 3 つの概念 — Instant、Local、Civil

プログラムで時間を扱うときにまず区別すべき 3 つ:

1.1 Instant (瞬間)

「今この瞬間」という絶対的な時点。地球のどこで観測しても同じ値になる。Unix timestamp (1970-01-01T00:00:00Z からの経過秒) が代表的表現。

例: 1760000000 (2025-10-09T07:33:20Z に相当する秒単位 epoch)

1.2 Civil Date/Time (民間時刻)

「2026 年 4 月 15 日 13:30」のように カレンダーと時計で読む値。タイムゾーンなしでは曖昧。

1.3 Zoned Date/Time (タイムゾーン付き民間時刻)

「2026-04-15 13:30 Asia/Seoul」。これで Instant に変換できる。

1.4 核心ルール

  • DB/ログ/ソート: Instant で保存 (UTC の時点)
  • UI 表示: ユーザーの tz に Zoned として変換
  • 繰り返しスケジュール (「毎月 1 日 9 時」) は Civil + TimeZone で保存

「UTC で保存すれば OK」は半分正しく半分間違い。「2026 年 12 月 25 日 00:00 にアラーム」というスケジュールをソウルのユーザーが設定した場合、UTC に換算して保存すると DST 変更や tz 変更時に 日付が変わってしまう。こうしたケースでは元の civil+tz を保持する必要がある。


2. UTC、GMT、TAI、UT1 — 時刻標準の混乱

2.1 GMT (Greenwich Mean Time)

英国グリニッジの平均太陽時。19 世紀に船舶航海のため標準化。法的には英国の一部公式文書で今でも使われるが、技術文書では「GMT」は ほぼ常に UTC の口語的同義語 として使われる。

2.2 UTC (Coordinated Universal Time)

国際標準。atomic time ベース で非常に精密だが、地球の自転と合わせるために leap second を挿入する。UTC = TAI - (累積 leap second 数)。

2.3 TAI (International Atomic Time)

世界約 400 台の原子時計の平均。leap second なし。2025 年時点で UTC より 37 秒 先行している。GPS time は UTC ではなく TAI ベースで、1980 年基準のオフセットを持つ。

2.4 UT1

地球自転に実際に合わせた時間。UTC は UT1 と 0.9 秒以上ずれてはならないという規約で leap second が追加される。

2.5 実務まとめ

  • プログラムで「UTC」と呼ぶもの: ほとんど UTC = epoch + 秒 (leap second 無視)
  • 厳密な UTC: leap second あり
  • 通常用途では無視できる差だが、金融 High-Frequency Trading や衛星通信同期などでは重要

3. Unix Timestamp — シンプルさの代償

3.1 定義

1970-01-01T00:00:00Z からの経過秒 (または ms/ns)。

  • POSIX 標準: 秒単位、leap second 無視
  • つまり 1 日は 正確に 86,400 秒 として扱われる

3.2 leap second はどこへ消えるのか

実際の地球時間で leap second が挿入されると UTC は 23:59:60 を一度通る。Unix time はこれをどう処理するか?

2 つの方式:

  1. Slew (推奨): leap second 前後の数時間にわたって時刻をゆっくり延ばして吸収。Google/AWS/Meta が採用。どの瞬間も「スキップ」されない。
  2. Smear: NTP サーバが leap second 当日に 86,400 秒ではなく 86,401 秒相当を slew。実際の OS clock もなめらかに進む。
  3. Jump: 23:59:59 から 00:00:00 に戻る。多くのシステムが crash したり重複イベントを生成する。

歴史的事故:

  • 2012 年 6 月 30 日の leap second: Linux カーネルのバグで多数の Java アプリが CPU 100% を占有。Reddit、LinkedIn、Foursquare がダウン
  • 2015 年 6 月 30 日の leap second: Twitter/Instagram が一時障害
  • 2017 年 1 月 1 日の leap second: Cloudflare DNS が leap second 計算エラーで一部障害

今後: 2022 年に国際度量衡総会 (CGPM) が 2035 年までに leap second を廃止 することを決定。以降は数十年単位で「leap minute」などより大きな単位で調整。

3.3 2038 年問題

Unix time を 32bit signed integer で保存すると 2038-01-19T03:14:07Z 以降オーバーフロー。32bit 組み込みシステム (IoT、古い DB フォーマット) の時限爆弾。

対策: ほとんどの現代 OS/言語は 64bit に移行済みだが、レガシーな C/C++ バイナリ、一部 DB スキーマ、ファイルフォーマット を点検する必要がある。time_t を明示的にサイズ確認。

3.4 紀元 0 年問題 vs 負の epoch

-62135596800 は西暦 1 年 1 月 1 日 UTC。それ以前 (紀元前) は protocol により表現可能/不可能。Java の Instant.MIN は -9999999-01-01、Instant.MAX は 9999999-12-31。


4. ISO 8601 — 文字列表現の標準

4.1 基本形式

2026-04-15                       日付
2026-04-15T13:30:00              ローカル時刻
2026-04-15T13:30:00Z             UTC
2026-04-15T13:30:00+09:00        ソウル時刻
2026-04-15T13:30:00.123456789Z   ナノ秒精度
2026-W16-3                       週基準 (ISO 週 16 の水曜)
P1Y2M10DT2H30M                   期間 (1 年 2 ヶ月 10 日 2 時間 30 分)
2026-04-15/2026-04-20            区間

4.2 RFC 3339 — ISO 8601 のサブセット

IETF がインターネットプロトコル用に制限したサブセット。小文字 T 許可、タイムゾーンオフセット必須など。

4.3 日常の落とし穴

  • 2026-04-15T13:30:00 (tz なし) → ローカル?UTC?パーサによって異なる (JavaScript new Date() はローカル、Python fromisoformat は naive)
  • Z vs +00:00 — 意味は同じだが文字列比較は異なる
  • 小数以下の秒精度がシステムによって異なる (ms vs μs vs ns)
  • +09 vs +0900 vs +09:00 — パーサのサポート範囲の差

教訓: 文字列でやり取りする際は必ず タイムゾーンオフセットを明示。パーサは ISO 8601 公式ライブラリ (Java Instant.parse、Python 3.11+ fromisoformat、JS Temporal.Instant.from) を使う。


5. タイムゾーン — 政治の時間

5.1 タイムゾーンの役割

UTC からのオフセットを決定する。ただしオフセットは:

  • 地域ごとに異なる
  • 歴史的に変化する (国が DST を導入/廃止、オフセット変更)
  • 政治的決定で 突然 変わる可能性がある

5.2 IANA Time Zone Database (tz database、zoneinfo、Olson DB)

1986 年から Paul Eggert が維持している タイムゾーン史の教科書Asia/SeoulAmerica/New_York のような名前をキーに、その地域が歴史的に使用したすべてのオフセット/DST ルール/変更履歴を含む。

例: Asia/Seoul は 1954 年 3 月 21 日に UTC+8:30 から UTC+9 へ変更。1961 年までに何度かオフセットが変わり、1987〜1988 年に DST があったが廃止。現在の韓国 API で「1955 年 1 月 1 日 00:00 Seoul」を UTC に変換するには、この歴史を知る必要がある。

5.3 tzdata 更新

毎年 5〜10 回新バージョンが出る。最近の変更:

  • 2024 年: エジプトが DST 再導入
  • 2023 年: メキシコの大部分で DST 廃止
  • 2022 年: チリの DST 日付変更
  • 2022 年: ヨルダン、シリアが永久 DST に移行

運用上の重要点:

  • コンテナイメージの tzdata バージョンを確認 (alpine は tzdata パッケージの別途インストールが必要)
  • JVM は TZupdater で最新 tzdata に更新可能
  • DB サーバ OS の tzdata が古いとアプリが間違ったオフセットを使う
  • Node.js は ICU 同梱だが、一部 slim ディストリは small ICU のみ → タイムゾーン制限あり (NODE_ICU_DATA 設定)

5.4 略語の曖昧さ

CST — どの国?

  • Central Standard Time (米国、UTC-6)
  • China Standard Time (UTC+8)
  • Cuba Standard Time (UTC-5)

IST — さらに深刻:

  • India Standard Time (UTC+5:30)
  • Ireland Standard Time (UTC+1)
  • Israel Standard Time (UTC+2)

ルール: 略語は表示用のみに使う。プログラムでは IANA 名 (Asia/SeoulAmerica/Chicago) を使え。

5.5 POSIX TZ 文字列

古い形式だが今も生きている: EST5EDT,M3.2.0,M11.1.0。絶対に使わず IANA 名を使え。


6. DST — 現場の地雷

6.1 「存在しない時刻」

2024 年 3 月 31 日のヨーロッパ、02:00 → 03:00 にジャンプ。02:30 は 存在しない

// JavaScript をよく知らない場合
new Date('2024-03-31T02:30:00+01:00').toISOString()
// "2024-03-31T01:30:00.000Z" — 実際には意図した 02:30 ではない

対策:

  • ユーザー入力の civil time が存在しなければエラーか forward shift (次の有効時刻に)
  • Java ZonedDateTime.of(...) + ZoneRulesProviderGap を処理可能
  • Temporal.ZonedDateTime.from({..., disambiguation: 'earlier'/'later'/'reject'/'compatible'})

6.2 「2 回現れる時刻」

2024 年 10 月 27 日のヨーロッパ、03:00 → 02:00 に戻る。02:30 は 2 回 存在する。

  • 同じ「2024-10-27 02:30 Europe/Berlin」の文字列が 2 つの UTC 時点 に対応
  • 金融ログは UTC 基準でソートするのが安全

6.3 カレンダー予約の惨事

「毎週月曜 09:00 ミーティング」を UTC に変換して保存したら:

  • ソウル (常に UTC+9) は UTC 00:00
  • ニューヨーク (DST あり) は UTC 14:00 ↔ UTC 13:00 (夏冬で異なる)

元の civil+tz で保存 するのが正解。DB カラムを (timestamptimezonerecurrence_rule) に分離する。

6.4 DST 廃止の流れ

EU は 2019 年に「2021 年から DST 廃止」を決定したが各国の意見対立で延期。米国は 2022 年の「Sunshine Protection Act」で永久 DST 法案が上院通過するも下院保留。トルコ/ロシア/アイスランド/中国などは既に廃止。

開発者視点: 廃止の決定は 数ヶ月前に突然 確定するので、tzdata 更新チェーンを常に維持する必要がある。


7. 言語別の datetime API

7.1 JavaScript の Date — 悪名高い設計

1995 年に Netscape が Java の java.util.Date を急いでコピーしたが、その Java API は後に廃止された (Joda Time / java.time に置き換え)。しかし JS は 数十年間互換性のために直せなかった

問題点:

  • ミュータブル: date.setMonth(3) が元を変更
  • 月が 0 から始まる: new Date(2026, 0, 15) は 2026 年 1 月 15 日
  • 文字列パース非一貫: new Date("2024-03-15") は UTC、new Date("2024/03/15") はローカル — ブラウザによって異なる
  • タイムゾーン処理不可: 「ローカル」か「UTC」の 2 択。任意の IANA zone サポートなし
  • 精度 ms: ナノ秒不可

Moment.js、date-fns、Luxon、Day.js のようなライブラリがこのギャップを埋めた。

7.2 Temporal — 2025 年の救世主

Stage 3 → 2024 年末にブラウザ/Node に順次実装。主要型:

  • Temporal.Instant: ナノ秒精度の UTC 時点
  • Temporal.ZonedDateTime: Instant + IANA timezone
  • Temporal.PlainDate / PlainTime / PlainDateTime: タイムゾーンなしの civil
  • Temporal.Duration: 期間
  • Temporal.Calendar: グレゴリオ暦/イスラム暦/和暦/仏暦など
  • Temporal.Now: Temporal.Now.instant() など
const now = Temporal.Now.zonedDateTimeISO('Asia/Seoul')
const oneHourLater = now.add({ hours: 1 })
const duration = now.until(oneHourLater) // Temporal.Duration

革新ポイント:

  • イミュータブル: すべての演算が新オブジェクトを返す
  • 明示的なタイムゾーン処理: disambiguation: 'reject' | 'earlier' | 'later' | 'compatible'
  • ナノ秒サポート
  • 閏日/閏年/各種暦を正確に処理

7.3 Python

  • datetime: 標準、tzinfo を直接渡すか zoneinfo モジュール (Python 3.9+) を使用
  • pytz (旧): astimezone() バグと normalize() の必要性で悪名高い
  • arrow: より親しみやすい API
  • pendulum: Ruby 風 DSL
  • 推奨: datetime + zoneinfo (stdlib)
from datetime import datetime
from zoneinfo import ZoneInfo
dt = datetime(2026, 4, 15, 13, 30, tzinfo=ZoneInfo("Asia/Seoul"))
dt.astimezone(ZoneInfo("America/New_York"))

7.4 Java

  • java.util.Date: 旧式、使うな
  • java.util.Calendar: 旧式
  • java.time (Java 8、JSR-310 / Joda ベース): モダン
    • InstantLocalDateTimeZonedDateTimeOffsetDateTime
    • イミュータブル、スレッドセーフ
  • Joda Time: Java 7 以前世代。Java 8+ では java.time を使用。

7.5 Go

  • time.Time は内部に wall clock と monotonic clock の両方を保持
  • タイムゾーンは time.LoadLocation("Asia/Seoul")
  • フォーマット文字列が独特: 2006-01-02 15:04:05 -0700 (参照日)
  • パース: time.Parse(layout, s)

7.6 Rust

  • std::time::SystemTime: OS 時計
  • std::time::Instant: monotonic (経過時間計測用)
  • Civil/zoned 処理は chrono または jiff (2024 年 BurntSushi 発表) crate
  • jiff は Temporal-inspired API で注目

8. Monotonic Clock vs Wall Clock

8.1 違い

  • Wall clock: カレンダー時間。NTP 同期で前後にジャンプし得る
  • Monotonic clock: 「起動以来の経過時間」に近いもの。絶対に戻らない

8.2 なぜ重要か

経過時間計測 には必ず monotonic を使う:

// NG — NTP sync が戻ると負の値になる
const start = Date.now()
await doWork()
const elapsed = Date.now() - start

// OK
const start = performance.now()
await doWork()
const elapsed = performance.now() - start

タイムアウト / rate limiter / ベンチマーク はすべて monotonic clock を使う必要がある。

8.3 言語別の monotonic アクセス

  • JS ブラウザ: performance.now() (ms + 小数、ページロードから)
  • JS Node: process.hrtime.bigint() (ナノ秒)
  • Go: time.Now() に monotonic が含まれる、time.Since(start) は常に安全
  • Java: System.nanoTime()
  • Python: time.monotonic()time.monotonic_ns()

9. NTP、PTP — 分散システムの時計同期

9.1 NTP (Network Time Protocol)

  • 階層化された「stratum」構造:
    • Stratum 0: 原子時計、GPS 受信機
    • Stratum 1: Stratum 0 に直接接続されたサーバ (NIST、KRISS)
    • Stratum 2: Stratum 1 から受ける公開サーバ (time.google.com、time.cloudflare.com)
    • Stratum 3+: 一般サーバ
  • UDP 123 番ポート
  • 精度: LAN 内で数 ms、インターネット距離で 10〜50 ms
  • ほとんどの OS に標準搭載 (chronydsystemd-timesyncdw32time)

9.2 PTP (Precision Time Protocol、IEEE 1588)

  • LAN 内でナノ秒〜マイクロ秒精度
  • スイッチが timestamping hardware をサポートする必要あり
  • 金融 High-Frequency Trading、5G 基地局同期で必須
  • AWS は 2023 年に「Time Sync Service」(PTP ベース、マイクロ秒精度) を公開

9.3 Google TrueTime

Spanner DB が世界規模で一貫したトランザクションを行うために作ったシステム。時刻を単一値ではなく [earliest, latest] の区間 として扱う。

  • 各データセンターに GPS + 原子時計を設置
  • TT.now() が {earliest, latest} を返す (通常 7 ms 幅)
  • トランザクション commit 時に latest を timestamp として採用し、latest 経過まで待つ → グローバル ordering を保証

類似アイデア: CockroachDB の HLC (Hybrid Logical Clock) — NTP + 論理時計の組み合わせ。

9.4 実務の教訓

  • NTP がない、または drift の大きいサーバは分散システムから 排除 すべき (信頼できない timestamp)
  • 時刻ベースのトークン (JWT exp) や rate limiter は 5〜10 秒の leeway を持たせて stratification エラーに耐える
  • Kubernetes ノードはすべて同じ NTP サーバを使うことを推奨

10. 暦体系 — グレゴリオ暦がすべてではない

10.1 主要な暦

  • グレゴリオ暦: 1582 年導入、現在の国際標準
  • ユリウス暦: グレゴリオ暦以前。一部の正教会が今も使用
  • イスラム暦 (Hijri): 月ベース、1 年約 354 日。宗教的日程の計算に必須
  • ヘブライ暦: 太陰太陽暦、閏月あり
  • 和暦: グレゴリオ暦 + 元号 (令和、2019〜)。API が元号遷移に対応する必要あり
  • タイ仏暦: グレゴリオ暦 + 543 年オフセット → 2025 年 = 仏暦 2568 年
  • ペルシア暦: イラン公式
  • 中国暦: 太陰太陽暦、24 節気。公式暦はグレゴリオだが伝統的な祝日 (春節) は旧暦

10.2 Unicode CLDR / ICU

多言語暦のサポート:

new Intl.DateTimeFormat('ja-JP-u-ca-japanese', { year: 'numeric', month: 'long', day: 'numeric' })
  .format(new Date('2026-04-15'))
// "令和8年4月15日"

new Intl.DateTimeFormat('ko-KR-u-ca-buddhist').format(new Date('2026-04-15'))
// "불기 2569년"

10.3 閏年ルール (グレゴリオ暦)

  • 4 で割り切れる → 閏年
  • 100 で割り切れる → 平年
  • 400 で割り切れる → 閏年

2000 年は閏年 (400)、1900 年/2100 年は平年 (100)。

「毎年 2 月 29 日のミーティング」は 4 年に 1 度しか発生しない。Outlook などは「前月末日」または「翌月 1 日」の fallback オプションを提供。

10.4 1752 年 9 月 3〜13 日の空白

英国は 1752 年にユリウス暦からグレゴリオ暦に切り替えた際、9 月 2 日の次が 9 月 14 日。ロシアは 1918 年。この歴史的空白を 正確にモデル化するライブラリは稀 (Java GregorianCalendar、Noda Time などが一部サポート)。


11. データベースの時刻処理

11.1 PostgreSQL

  • timestamp without time zone: civil time。保存/取得時はそのまま。
  • timestamp with time zone (timestamptz): 内部は UTC epoch、入出力時にセッションの TIME ZONE 設定で変換。
  • datetimeinterval サポート
  • AT TIME ZONE 'Asia/Seoul' で変換

推奨: 特別な理由がなければ timestamptz を使う。

11.2 MySQL

  • DATETIME: civil time、タイムゾーン変換なし
  • TIMESTAMP: 保存時にセッション tz → UTC、取得時に UTC → セッション tz (自動)。2038 問題あり (32bit)。
  • DATETIME サポート

注意: TIMESTAMP はサーバの time_zone セッション変数によって結果が変わる。コネクションごとに SET time_zone = '+00:00' を推奨。

11.3 MongoDB

  • BSON Date は 64bit int のミリ秒 (UTC epoch)
  • タイムゾーンなし → 保存前に UTC 変換必須

11.4 DynamoDB、Redis

  • タイムスタンプ文字列 (ISO 8601 with offset) または epoch ms を保存
  • TTL は Unix timestamp (秒)

11.5 ClickHouse

  • DateTimeDateTime64 (サブ秒精度)
  • 各カラムがタイムゾーンを持てる (DateTime('Asia/Seoul'))
  • 集計クエリで toTimeZone() 関数で変換

12. ログとメトリクスの timestamp

12.1 原則: UTC で保存、ローカルで表示

  • サーバログ: すべて UTC (TZ=UTC 設定)
  • ダッシュボード (Grafana、Kibana): ユーザーブラウザの tz で表示
  • 分散トレーシング: span の timestamp は UTC epoch ns

12.2 Correlation と Clock Skew

マイクロサービス A、B、C があり、A が B を呼び、B が C を呼ぶ。各サーバの時計が少しずつ異なると:

  • B の span が A の span より 先に 始まったように見えることがある
  • OpenTelemetry はこれを緩和するため、span の duration を monotonic clock で計測してから UTC に換算する

12.3 ISO 8601 または RFC 3339 で保存

2026-04-15T13:30:00.123456789Z

ここで Z (= UTC) は必須。tz なしの文字列はパースが曖昧になる。

12.4 Kafka など Event Stream

各メッセージは producer timestamp (producer が付けた時刻) と log append time (broker が保存した時刻) の 2 つを持つ。順序ソートは offset で行い、時刻は参考用。


13. 実務バグケース 10 選

13.1 「夜中 2 時に実行される cron が DST の日に実行されない」

原因: 2:30 が存在しないタイムゾーン。cron 式はローカル時刻基準。 解決: UTC で cron を回すか、OnCalendar=UTC オプションを明示。

13.2 「JWT exp が妙に早く期限切れになる」

原因: サーバとクライアントの時計差。 解決: 5〜30 秒の leeway。NTP 同期を強制。

13.3 「メールの 'Received' ヘッダの時刻が未来」

原因: どこかの中継サーバの時計が進んでいる。 解決: インフラチームに NTP 点検を依頼。Spam filter はこのケースを疑わしい信号として扱う。

13.4 「誕生日通知が 1 日早く/遅く来る」

原因: UTC 基準で保存したがユーザーが別の tz に移動。 解決: 誕生日は PlainDate で保存 (時刻なし)。通知送信時にユーザーの tz の 00:00 にマッピング。

13.5 「1970-01-01 と表示される」

原因: null timestamp を 0 として扱い Date で format。 解決: null チェック。

13.6 「2038 年のテストが失敗」

原因: 32bit time_t。 解決: 64bit コンパイルを確認。

13.7 「duration が負」

原因: wall clock を使用。NTP が後ろに補正。 解決: monotonic clock (performance.nowtime.monotonic)。

13.8 「イベントログの順序がバラバラ」

原因: サーバ間の clock skew。 解決: OpenTelemetry の Span Link + Logical Clock。

13.9 「ユーザーが『今日』予約したのに『明日』として保存される」

原因: ブラウザ tz ≠ サーバ tz。サーバで new Date(...) でパースするとサーバ tz 基準。 解決: ブラウザで Temporal.PlainDate.from(...) または ISO 文字列 + tz オフセット明示。

13.10 「休暇日数の計算が DST 切り替え時に 0.96 日」

原因: (endMs - startMs) / (24 * 3600 * 1000) で DST により 1 日が 23 または 25 時間になる。 解決: ChronoUnit.DAYS.between(zonedStart, zonedEnd) のような civil-aware な演算。


14. チェックリスト — 時刻関連システム設計

保存

  • 「Instant」と「Civil+TZ」を区別して保存
  • UTC 保存優先、繰り返しスケジュールは元の civil+tz を保持
  • DB timestamptz を使用 (MySQL は接続 tz を UTC に固定)
  • ISO 8601 + timezone offset 文字列
  • ナノ秒精度が必要か確認 (不足なら付加カラム)

変換

  • ユーザー tz を profile に保存 (IANA 名)
  • UI 表示時のみユーザー tz に変換
  • サーバローカル tz に依存しない (TZ=UTC 強制)

DST

  • 「存在しない時刻」のハンドリング (reject / next valid)
  • 「2 回現れる時刻」のハンドリング (earlier / later)
  • テストデータに DST 切り替え日を含める

同期

  • NTP または chrony が稼働しているか確認
  • Clock skew モニタリング (5 秒以上で alert)
  • 経過時間は monotonic clock

ライブラリ

  • JS: Temporal (または Luxon) — Date 直接使用は避ける
  • Java: java.time (Date/Calendar は避ける)
  • Python: datetime + zoneinfo
  • Go: time (サポートあるが layout フォーマットに注意)
  • Rust: jiff または chrono

運用

  • tzdata を定期更新
  • Docker イメージの tzdata バージョン確認 (alpine)
  • JVM の TZupdater 適用プロセス
  • 新規 DST 政策変更のニュース監視

15. 歴史的事件アーカイブ

15.1 2007 年の米国 DST 日付変更

米政府が DST 開始を 3 週間前倒し。多くの Outlook/Blackberry が旧ルールで動き、ミーティングが 1 時間ずれた。MS が緊急パッチを配布。

15.2 2011 年のサモアのタイムゾーン変更

国際日付変更線を越えたため 2011 年 12 月 30 日が存在しなかった。12 月 29 日の次が 12 月 31 日。ニュージーランドの取引時間帯に合わせるため。

15.3 2016 年のトルコ

当日決定で DST を廃止。古い Android 端末が自動変更されず、1 時間遅れて出勤。

15.4 2018 年の北朝鮮

UTC+8:30 → UTC+9 に変更 (南北統一のジェスチャー)。tzdata 更新。

15.5 2023 年のグリニッジ標準時の死?

UK 政府が Brexit 後のタイムゾーン政策を議論。まだ変化なし。

15.6 金融の High-Frequency Trading

NASDAQ などは ナノ秒 精度で注文順序を記録。PTP + 共有 atomic clock。1 マイクロ秒の差が数百万ドルの取引結果を左右する。


結び — 「時間は複数ある」

本稿で繰り返した視点の 1 つ: 「時間」という単一概念は存在しない。Instant、Civil time、Zoned time、Monotonic、Logical time など複数の層の時間があり、それぞれ異なる目的に適している。プログラムのバグの多くはこれらの層を混同したときに発生する。

核心原則 5 つ:

  1. 保存は UTC、表示はローカル — そして繰り返しスケジュールは元の civil+tz を保持
  2. ソート・計算は Instant で、UI は ZonedDateTime で
  3. 経過時間は Monotonic で (wall clock はジャンプし得る)
  4. タイムゾーンは IANA 名で (略語禁止)
  5. DST 切り替え日をテストケースに含める

Unicode と同様、時間も 人間社会の複雑さ がソフトウェアに染み込んだ領域だ。政治的決定で DST が変わり、宗教暦によって祝日が変わり、地球の自転が遅くなって leap second が挿入される。「Just use UTC」では解決しない現実において、開発者は 時間のレイヤードモデルを頭の中に持つ 必要がある。

次回の記事では 分散システムの Consensus — Raft/Paxos、CAP 定理、そして eventual consistency を掘り下げる。本稿で見た 時間の不一致 が分散システムの核心的難題とどう絡むか (Lamport clock、Vector clock、Hybrid Logical Clock など) を続けて見ていこう。

時間を理解することは単にバグを避ける技術ではない。「同時に起きた」とは何を意味するのか という哲学的問いでもある。相対性理論が語るように、分散システムで「全世界共通の現在時刻」は存在しない — 我々はその近似を扱っているにすぎない。