メインコンテンツへスキップ

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples on this page work on VillageSQL. Install Now →
タイムゾーンのバグはデバッグが最も難しいものの 1 つです。サーバーの場所によって現れたり消えたりし、開発環境と本番環境で違う形で現れ、夏時間の切り替えの後になって初めて表面化することもよくあります。根本原因はほぼ常に、MySQL が時刻を保存する方法と、アプリケーションが想定している読み取り方の不一致です。

DATETIME と TIMESTAMP

この 2 つの型は似て見えますが、肝心の瞬間 — 取得時 — に違う振る舞いをします。 TIMESTAMP は値を自動的に変換します。
  • INSERT/UPDATE 時: セッションの time_zone から UTC に変換して保存する
  • SELECT 時: UTC からセッションの time_zone に戻して表示する
DATETIME は変換を行いません。書き込んだ値がそのまま返ってきます。 'America/New_York' のような名前付きゾーンは、タイムゾーンテーブルがロードされている必要があります(下の名前付きタイムゾーンを参照)。初期化したばかりのサーバーにはそれがありません。下の例を実行する前に SELECT COUNT(*) FROM mysql.time_zone_name; で確認し、0 であれば先にロードするか、'-05:00' のような数値オフセットに置き換えてください。

2038 年問題

TIMESTAMP は 32 ビットの Unix タイムスタンプとして保存されます。2038-01-19 03:14:07 UTC より後の日付は表現できません。将来の日付を保存するアプリケーション — 予約、サブスクリプション、予定されたイベント — は、この制限に当たらないように DATETIME を使うべきです。

サーバーとセッションのタイムゾーン

MySQL には独立した 2 つのタイムゾーン設定があります。
time_zone が SYSTEM を示す場合、MySQL はオペレーティングシステムのタイムゾーンを使っています。これは OS が変わる(サーバー移行、夏時間の切り替え)までは動作しますが、変わるとタイムスタンプがずれます。 ベストプラクティス: サーバーを明示的に UTC に設定し、表示のための変換はアプリケーションで行います。
または実行時に設定します。

名前付きタイムゾーン

'+00:00' と 'UTC' はどちらも有効ですが、'America/New_York' のような名前付きゾーンはタイムゾーンテーブルがロードされている必要があります。CONVERT_TZ() が NULL を返す、または名前付きゾーンが失敗する場合、テーブルがありません。

AUTO_UPDATE 列

TIMESTAMP 列は、挿入時と更新時の自動設定をサポートします。
セッションのタイムゾーンが UTC であれば CURRENT_TIMESTAMP は UTC で評価されるので、誰がどのタイムゾーンから接続しても、created_at と updated_at は一貫して保存されます。 DATETIME と NOW() でも同じことができます。
NOW() はセッションのタイムゾーンでの現在の日付と時刻を返します。これも、セッションを UTC に統一する理由の 1 つです。

よくある質問

タイムスタンプは DATETIME に UTC で保存すべきですか、それとも TIMESTAMP を使うべきですか?

日付が 2038 年の上限内に収まるなら、TIMESTAMP のほうがすっきりします。UTC への変換を自動的に処理するからです。2038 年を超える将来の日付や、1970 年より前の過去の日付を保存する必要があるアプリケーションでは、DATETIME を使い、挿入前に常に UTC に変換することでアプリケーション層で UTC を強制します。

サーバー移行の後にタイムスタンプがずれるのはなぜですか?

古いサーバーは time_zone = SYSTEM で、ある OS のタイムゾーンを指していました。新しいサーバーは別のタイムゾーン(または UTC)を指しています。UTC で保存された TIMESTAMP 値はどちらのタイムゾーンでも正しく表示されますが、UTC ではなくシステムのタイムゾーンを前提に保存された値があれば、それらはずれます。移行の前に、両方のサーバーで SELECT @@global.time_zone を実行して確認してください。

時刻の部分を持たない日付だけを保存するにはどうすればよいですか?

DATE を使います。3 バイトで保存され、時刻やタイムゾーンのあいまいさがありません。WHERE date_col = '2024-03-15' のような操作は、セッションのタイムゾーンに関係なくあいまいさがありません。

トラブルシューティング

関連項目