広告が嫌いですか? 行く 広告なし 今日

タイムゾーンは嘘である(そしてコードでどう扱うか)

更新日

タイムゾーンはUTCオフセットのように見えますが、実際はそうではありません。このガイドでは、タイムゾーンがコードを破壊する理由(夏時間のギャップ、半時間オフセット、ナウな日時)を解説し、JavaScript、Python、PHP、SQLで正しい方法で扱う方法を紹介します。

タイムゾーンは嘘です(そしてコードでどう扱うか)1

あなたはサーバーに現在の時刻を尋ねました。それは14:00と答えてくれました。その情報を保存しました。その後、その時刻を再び問い合わせました。今度は16:00と表示されています。何も変更していません。これは時差帯です。

時差帯はソフトウェアにおける最も見かけ上単純だが実際には複雑な問題の一つです。表面的にはUTCオフセットという単純な計算に見えますが、実際には政治的決定、歴史的な偶然、半時間オフセット、そして突然変化する夏時間規則が混ざった、非常に複雑な構造です。この記事では、なぜ時差帯がこんなに難しいのか、そしてコードで正しい方法でどのように扱うべきかを解説します。

「UTCを使うだけ」は半分の答えである

最もよく耳にされるアドバイスは、「すべてをUTCで保存する」というものです。このアドバイスは正しいですが、不完全です。UTCでの保存は一貫性のある保存を解決しますが、表示や入力という大きな問題を解決していません。 1つ 問題(一貫した保存)を解決しながら、より大きな問題を放置しています。

東京に住むユーザーが9時(彼らの時刻)に会議を予約します。その情報をUTCとして保存します。その後、ニューヨークに住むユーザーが同じイベントを開きます。そのイベントは彼らのローカル時刻で表示されます。しかし、ユーザーが旅行しているとき、どのローカル時刻が適用されるのでしょうか?システム時計を更新したとき?夏時間規則が開始されたとき?「UTCを保存し、ローカル時刻で表示する」は良いポリシーですが、これは実行がほとんどのチームを破壊します。 実行 がほとんどのチームを破壊します。

時差帯の実際の問題

解決策に至る前に、実際に戦っているものを名前をつけることが助けになります。

1. UTCオフセットは時差帯ではない

UTC+5:30 は「インド時間」ではありません。それは静的なオフセットです。インド標準時間は Asia/Kolkata — 名前付き時差帯であり、+5:30のオフセットを使用しており、夏時間規則を観察していません。これらは異なるものです。オフセットをハードコードすると、数値が保存されます。名前付き時差帯を保存すると、意図が保存されます。

名前付き時差帯は IANA時差帯データベース (tzdataまたはOlsonデータベース)に存在します。すべての重要な言語やOSはそのコピーを搭載しています。それを常に使用してください。常に America/New_YorkUTC-5.

に優先してください。

2. 夏時間規則は時計を移動させます(予測不可能に)

  • 夏時間規則の遷移は、2つの本質的に危険なエッジケースを作ります: 「春の前進」ギャップ:
  • 時計は2:00AMから3:00AMにジャンプします。2:30AMの時間は実際に存在しません。その日に2:30AMに予約を試みると、エラーまたは静的誤動作が発生する可能性があります。その結果は、使用しているライブラリによって異なります。 「秋の後退」の曖昧性: 時計は2:00AMから1:00AMに戻ります。1:30AMの時間は現在2回

現れます。追加の文脈(折り返しフラグ)がないと、どの発生を意味しているかわかりません。

そして、夏時間規則は変化します。国や米国州は、過去数年間にかけて規則を変更、廃止、または変更しています。あなたのコードの動作は、最新のtzdataパッケージを持っているかどうかに依存します。これは開発者の問題ではなく、運用の問題です。

3. すべてのオフセットが整数時間ではない

インドはUTC+5:30、ネパールはUTC+5:45、オーストラリアの一部はUTC+9:30、イランは冬はUTC+3:30、夏はUTC+4:30です。もしシステムが時差帯が常に整数時間であると仮定しているなら、数百万のユーザーのタイムスタンプが静黙的に破壊されます。

4. サーバー上の「ローカル時刻」は意味がない UTCサーバーはシステム時計を持ちます。その時計には設定された時差帯があります。それは new Date() または datetime.now() 、あるいはホスティングプロバイダーがデフォルトに設定したもの、あるいは数年前のシステム管理者が設定したものかもしれません。コードが

を呼び出す際に時差帯を指定しない場合、そのサーバー設定に依存しています。異なる環境では異なる結果が得られます。これはすべてのデプロイメントで発生するバグの準備です。

言語ごとの正しいアプローチ

JavaScript / TypeScript Date オブジェクトはJavaScriptのネイティブなUTCミリ秒タイムスタンプの薄いラッパーです。見た目は親しみやすいですが、実際にはそうではありません。手動でフォーマットしないでください。表示には Intl.DateTimeFormat APIを使用するか、ライブラリを活用してください。

現代の標準はTemporalです — Chrome 121でリリースされたTC39の提案で、すべての主要なランタイムに導入されています。時差帯に第一クラスのサポートを持ち、長期的な解決策として適しています:

// Store an instant (UTC-equivalent)
const meeting = Temporal.Instant.from("2025-06-15T14:00:00Z");

// Display in a specific zone
const nyTime = meeting.toZonedDateTimeISO("America/New_York");
console.log(nyTime.toString()); // 2025-06-15T10:00:00-04:00[America/New_York]

// Convert to Tokyo time
const tokyoTime = meeting.toZonedDateTimeISO("Asia/Tokyo");
console.log(tokyoTime.toString()); // 2025-06-16T23:00:00+09:00[Asia/Tokyo]

Temporalを使用できない場合、 date-fns-tz とdate-fnsを組み合わせて信頼できる選択肢です。 Luxon は別の信頼できる選択肢です。Moment.jsは機能が完全ですが、もうメンテナンスされていません—それから移行してください。

パイソン

Pythonの datetime モジュールは「ナイブ」datetime(時差帯情報なし)と「アウェア」datetime(tzinfo付き)を区別します。ナイブdatetimeは罠です—それは有効な時刻のように見えますが、システム間で意味を持ちません。

常にアウェアdatetimeを使用してください。Python 3.9以降では zoneinfo モジュール (Python 3.9+)でIANA時差帯サポートを使用してください:

from datetime import datetime
from zoneinfo import ZoneInfo

# Aware datetime — always do this
utc_time = datetime(2025, 6, 15, 14, 0, 0, tzinfo=ZoneInfo("UTC"))

# Convert to New York time
ny_time = utc_time.astimezone(ZoneInfo("America/New_York"))
print(ny_time)  # 2025-06-15 10:00:00-04:00

# Never do this — naive datetime, meaningless
bad = datetime(2025, 6, 15, 14, 0, 0)  # what zone is this?

Python 3.8以前では pytz ライブラリを使用しますが、 pytzのlocalize/normalize APIは、足元の罠を避けるべきです。 zoneinfo クラスはIANA時差帯をネイティブにサポートしており、

PHP

と良好にシリアル化できる。 DateTimeDateTimeImmutable を介してサポートしています。より安全なのは DateTimeZone— なぜなら、変更は新しいオブジェクトを返すため、場所を変更しないからです。 DateTimeImmutable SQL / データベース

$utc = new DateTimeImmutable('2025-06-15T14:00:00', new DateTimeZone('UTC'));

// Convert to Sydney time
$sydney = $utc->setTimezone(new DateTimeZone('Australia/Sydney'));
echo $sydney->format('Y-m-d H:i:s T'); // 2025-06-16 00:00:00 AEST

// Store timestamps as ISO 8601 strings or Unix timestamps
echo $utc->getTimestamp(); // 1749996000

データベースの時差帯処理は、自らの地雷です。

PostgreSQL:

  • (timestamp with time zone) — すべてをUTCとして保存し、出力時に変換します。ユーザー向けデータには プレビューするには TIMESTAMPTZ (without time zone)を使用しないでください;それはあなたが与えるものそのものを受け入れ、変換を行いません。 TIMESTAMP MySQL:
  • タイプはナイブ(時差帯なし)です。UTC保存が必要ならDATETIME を使用しますが、範囲は2038年までです。新しいスキーマでは、ISO 8601形式またはUnixタイムスタンプとして TIMESTAMP を保存するのが安全です。 VARCHAR SQLite: BIGINT はネイティブな時差帯タイプを持ちません。ISO 8601テキスト(
  • )またはUnix整数として保存します。変換はアプリケーションコードで行います。 使用するデータベースに関わらず、サーバーのセッション時差帯を明示的に設定してください。デフォルトに頼らないでください。2025-06-15T14:00:00Z実用的なルールで実際に問題を防ぐ

理論を経て、ここに実際のルールを示します。これらはほとんどの時差帯のバグを防ぎます:

すべてをUTCで保存してください。

データベース内のすべてのタイムスタンプはUTCで保存すべきです。内部用途の例外は存在しません。

  1. オフセットではなく、IANAゾーン名を使用してください。 必要に応じて、UTCタイムスタンプと共に
  2. を保存してください。オフセットだけでは、夏時間規則の遷移後に再構築できません。 ゾーンを指定しない「now」を呼び出さないでください。 America/Chicago — 常に明示的な時差帯引数を渡すか、すぐに時差帯を付与してください。
  3. 最終的にローカル時刻に変換してください。 datetime.now(), new Date(), time() すべての日付計算はUTCで行い、ユーザーのローカル時差帯に変換するのは表示直前にだけ行いましょう。
  4. tzdataを常に最新に保ちます。 時差帯規則は変化します。tzdataパッケージの更新は、定期的な依存関係管理の一部として行いましょう。
  5. 夏時間規則の遷移をテストしてください。 年間の2つの危険な日(春の前進、秋の後退)は、スケジュールや時間敏感なデータを処理する場合、テストケースに含まれるべきです。
  6. ユーザーに時差帯を明示的に尋ねてください。 IP地理位置やブラウザの推測に重要なデータを頼らないでください。時差帯選択器を表示し、その結果を保存してください。
  7. Unixタイムスタンプについての一言 Unixタイムスタンプ—1970-01-01T00:00:00Zからの秒数—は、時差帯に依存しない定義で、時差帯に無関係です。これはログ、API、キャッシュなど、一貫性のある単一の数値を必要とする場面で非常に有効な保存形式です。

ただし、Unixタイムスタンプはユーザーの

元の意図を

持ちません。たとえば、誰かが「土曜日の朝、ロンドン時間」でフライトを予約し、その情報だけをUnixタイムスタンプとして保存した場合、ロンドンが意味する「ロンドン時間」を失っています。3ヶ月後にフライトを再予約し、ロンドンが夏時間規則に入ったり出たりした場合、間違ったローカル時刻が表示される可能性があります。ユーザーの意図が重要である場合は、タイムスタンプと共に時差帯を保存してください。 最も難しいケース:繰り返しイベント繰り返しイベント—週間のスタンドアップ、月間の請求サイクル、日間のリマインダー—は、すべての時差帯のエッジケースを同時に暴き出します。

たとえば、ロサンゼルスに住むユーザーに対して「毎月月曜日9時」のルールを設定します。9時を太平洋時刻で保存しますか?彼らが東京へ旅行したときどうなるか?時計が前進し、その月曜日が遷移日となったときどうなるか?

唯一正しいモデルは:

繰り返しルールを「月曜日09:00 America/Los_Angeles」という壁時刻+IANA時差帯として保存する

スケジュール時にUTCの次の発生時刻を計算する、ルール作成時ではなく

  • 繰り返し期間中にDST遷移が起こった場合、再計算する
  • 適切な時差帯コンテキストを与えられた場合、JSの
  • (JS)および

(Python)はこの処理を正しく行います。この論理を手動で実装することは、数ヶ月後に表面化する微妙なバグを引き起こす確実な道です。 rrule.js (JS) および dateutil.rrule (Python) が適切な時区のコンテキストを与えられたときにはこれを行うことが正しく処理されます。この論理を手動で実装することは、数か月後に現れる微妙なバグを引き起こす確実な道です。

広告なしで楽しみたいですか? 今すぐ広告なしで

拡張機能をインストールする

お気に入りのブラウザにIOツールを追加して、すぐにアクセスし、検索を高速化します。

に追加 Chrome拡張機能 に追加 エッジ拡張 に追加 Firefox 拡張機能 に追加 Opera 拡張機能

スコアボードが到着しました!

スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!

ニュースコーナー 技術ハイライト付き

参加する

価値ある無料ツールの提供を継続するためにご協力ください

コーヒーを買って