タイムゾーンは嘘である(そしてコードでどう扱うか)
タイムゾーンは地図に見えて簡単ですが、実際にはそうではありません。なぜタイムゾーンがソフトウェアを破壊するのか、そしてコードで扱うための正しい思考モデルを紹介します。
あなたは地図を開きます。縦のスライスが見えます。「UTC-5は東部時間、UTC+1は中央ヨーロッパ時間です。」シンプルでしょ?
間違っています。時差は地理的現実ではなく、政治的な構造です——この違いが、ソフトウェア開発におけるいくつかの厄介なバグを回避します。
時差が実際には混乱している理由
以下は、あなたのオペレーティングシステムのtzデータベースが静かに代わりに処理しているものです:
- 夏時間(DST) — すべての国がそれを遵守しているわけではありません。各国はルールを変更し、場所によって切り替えの日が異なります。アメリカは2007年に日付を変更しました。エジプトは2011年にDSTを廃止し、その後再導入し、再び廃止しました。
- 半時間および四分の一時間のオフセット — インド(UTC+5:30)、ネパール(UTC+5:45)、およびオーストラリアの一部(UTC+9:30)が存在します。オフセットが1時間単位であるというあなたの前提は誤りです。
- 歴史的な変更 — ロシアは2014年にUTC+4からUTC+3に移行しました。サモアは2011年12月30日に1日をスキップして、国際時差線の一方から他方へ移動しました。過去のタイムスタンプを扱う場合、正しいオフセットは今日とはまったく異なることがあります。
- 曖昧な時間 — クロックが戻るとき、1時30分が2回起こります。クロックが進むとき、2時30分はまったく存在しません。もし「2024年11月3日 1時45分 東部時間」をUTCオフセットなしで保存するなら、曖昧なタイムスタンプになります。
これらはすべてトリビアではありません。すべてのエッジケースが、実際の生産環境でのバグを引き起こしました——カレンダーイベントの欠落、二重請求、スケジューリングシステムの破損など。
ほとんどの問題を解決する1つのルール
UTCに保存し、表示時のみローカル時間に変換します。
これは議論の余地のないアドバイスです。すべての主要なデータベース、API標準、分散システムチームの共通認識です。もしデータベースのカラムがUTCを保存しているなら、その時点を正確に把握できます——サーバーの場所、ユーザーの時差、DSTが発生したかどうかに関係なく。 2024-11-03 06:45:00 UTCを保存しているなら、その時点を正確に把握できます——サーバーの場所、ユーザーの時差、DSTが発生したかどうかに関係なく。
その時点を 2024-11-03 01:45:00 UTCオフセットなしで保存すると、情報が失われます。あなたが作成したタイムスタンプは、2つの異なる意味を持つようになります。
JavaScriptで時差を扱う方法
JavaScriptの標準搭載 Date オブジェクトは、Unixエポック(1970年1月1日UTC)からのミリ秒数として時間を保存しており、これは良い点です。問題はそのAPIが混乱し、不一致であるということです。
避けるべきこと
// BAD: Parsing without explicit timezone
const d = new Date('2024-11-03 01:45:00');
// Interpreted as LOCAL time — behavior varies by environment
// BAD: Storing user-facing strings as dates
const meeting = '3:00 PM Thursday';
// This is meaningless without a timezone
代わりに使うべきこと
// GOOD: Always use ISO 8601 with explicit UTC offset
const d = new Date('2024-11-03T06:45:00Z'); // Z = UTC
// GOOD: Display in user's local timezone using Intl API
const formatter = new Intl.DateTimeFormat('en-US', {
timeZone: 'America/New_York',
dateStyle: 'full',
timeStyle: 'short',
});
console.log(formatter.format(d)); // "Sunday, November 3, 2024 at 1:45 AM"
シンプルな日付フォーマットを越える場合は、 Luxon ライブラリを使用してください。時差サポートが第一クラスであり、DSTの遷移を正しく処理し、コードの意図を明確にします。
import { DateTime } from 'luxon';
// Parse an ISO string and convert to a specific zone
const utc = DateTime.fromISO('2024-11-03T06:45:00Z');
const eastern = utc.setZone('America/New_York');
console.log(eastern.toLocaleString(DateTime.DATETIME_FULL));
// "November 3, 2024, 1:45 AM EDT"
Pythonで時差を扱う方法
Pythonの datetime モジュールは、 ナイブ (時差なし) と アウェア (時差あり) のdatetimeオブジェクトを区別します。ナイブなdatetimeは危険なトリックです——時差境界を越えるコードでは避けましょう。
from datetime import datetime, timezone
import zoneinfo # Python 3.9+
# BAD: naive datetime (no timezone info)
d = datetime(2024, 11, 3, 1, 45, 0)
# GOOD: always attach a timezone
d_utc = datetime(2024, 11, 3, 6, 45, 0, tzinfo=timezone.utc)
# Convert to a specific timezone
eastern = zoneinfo.ZoneInfo('America/New_York')
d_eastern = d_utc.astimezone(eastern)
print(d_eastern) # 2024-11-03 01:45:00-05:00
古いPythonまたはより複雑なスケジューリングニーズの場合、 python-dateutil と 矢印 はどちらもよく維持されています。重要な原則は変わりません:UTCで作業し、境界で変換します。
データベースで時差を扱う方法
データベースの時差処理は、経験豊富な開発者でも誤りを起こすことがあります。
PostgreSQL
PostgreSQLには2つのタイムスタンプタイプがあります: timestamp (時差なし) と timestamptz (時差あり)。名前から見ると、時差が保存されているように見えますが、実際には時差は保存されていません——書き込み時にUTCに変換され、読み取り時にセッション時差に変換されます。これは正しい動作です。常に timestamptz は時差を保存しません——入力したものはそのまま出力されます、変換はありません。 timestamptz.
-- BAD
CREATE TABLE events (created_at TIMESTAMP);
-- GOOD
CREATE TABLE events (created_at TIMESTAMPTZ);
-- Querying across timezones
SELECT created_at AT TIME ZONE 'America/New_York' FROM events;
MySQL
MySQLの DATETIME は書き込み時にUTCに変換され、読み取り時に現在のサーバー時差に変換されます——しかし、1970年から2038年までの日付に限定されています(Unixタイムスタンプのオーバーフロー)。実際には: TIMESTAMP カラムを使用し、アプリケーションが常にUTC値を明示的に書き込むようにしてください。 DATETIME 複数の時差を扱うスケジューリング:隠れた罠
ユーザーが「毎月月曜日9時、ニューヨーク時間」の繰り返し会議を予約するとします。次の発生時をUTCタイムスタンプとして保存します。問題ない——DSTの変更が起こるまで。そして、DSTの変更後、あなたが保存したUTC時間は、ニューヨーク時間の10時に対応するようになります。
解決策:繰り返しイベントの絶対的な未来タイムスタンプを保存しないでください。代わりに、
ローカルルール — 時間帯識別子とローカル時間—to 保存し、実行時に次のUTCタイムスタンプを計算します。これにより、DSTの遷移後にシステムが正しい値を再計算できます。 IANA時差名とUTCオフセット
-- Store the intent, not the absolute moment
CREATE TABLE recurring_events (
id SERIAL PRIMARY KEY,
timezone TEXT NOT NULL, -- 'America/New_York'
local_time TIME NOT NULL, -- '09:00:00'
recurrence TEXT NOT NULL -- 'WEEKLY:MONDAY'
);
-- Compute next_occurrence_utc in application code at dispatch time
ユーザーの時差設定を保存する際、
IANA名(America/New_York, Europe/London, Asia/Kolkata)— UTCオフセット(UTC-5, UTC+1)ではなく、保存してください。オフセットはスナップショットであり、IANA名はDSTの歴史と将来のルールを完全にエンコードしています。
America/New_York は UTC-5 冬と UTC-4 夏。もし UTC-5を保存すると、半分の年間で間違ったオフセットを組み込んでしまうのです。
ルールの迅速な参考
- タイムスタンプをUTCとして保存する — データベースまたはAPI応答において、例外は一切ありません。
- ユーザーの設定や繰り返しスケジュールには、IANA時差名を使用する — UTCオフセットではなく、保存してください。
- 明示的な時差を持たない日付文字列をパースしないでください — 隠れた動作は環境に依存します。
- 表示レイヤーでローカル時間に変換する — ビジネスロジックやデータベースクエリでは変換しないでください。
- 繰り返しスケジュールの場合、ルールを — 事前に計算されたUTC時間ではなく、保存してください。これにより、DSTの遷移が将来の発生時を腐食しません。
- エッジケースでテストする — DST遷移時刻、国際時差線に近い日付、かつ時差が変更された地域の歴史的なタイムスタンプ。
恵 スコアボードが到着しました!
スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!
