DATETIMEとTIMESTAMPの違い:タイムゾーンと選び方

round black and white analog alarm clock SQL

MySQLで日時を入れる列を作るとき、DATETIMEとTIMESTAMPのどちらにするか迷った経験はないでしょうか。見た目はどちらも「2026-10-27 09:00:00」ですが、中身の扱いはかなり違います。大きな差は3つです。TIMESTAMPはタイムゾーンを変換して保存し、扱える範囲は2038年1月までに限られます。DATETIMEは入れた値をそのまま保存し、9999年まで扱えます。この記事では、2つの型の違いを実際に動くSQLで確かめ、用途ごとの選び方を表に整理します。

このシナリオで考える

イベント予約サービスのテーブルを例に考えます。開催日時(start_at)と、レコードを作った日時(created_at)を持つシンプルな構成です。違いを確かめるため、同じ日時をDATETIME列とTIMESTAMP列の両方に入れるテーブルも用意します。

CREATE TABLE events (
  id         INT AUTO_INCREMENT PRIMARY KEY,
  title      VARCHAR(100) NOT NULL,
  start_at   DATETIME     NOT NULL,                    -- 開催日時
  created_at TIMESTAMP    NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- 比較用:同じ値を両方の型に入れる
CREATE TABLE tz_demo (
  id INT PRIMARY KEY,
  dt DATETIME,
  ts TIMESTAMP NULL
);

以降の例は、MySQL 8.0の標準設定(strictモード有効)を前提に進めます。

DATETIMEとTIMESTAMPの違いを表で比べる

まずは仕様の違いを一覧で押さえましょう。数字はMySQL 8.0公式リファレンスの記載にもとづきます。

項目 DATETIME TIMESTAMP
扱える範囲 1000-01-01 00:00:00 〜 9999-12-31 23:59:59 1970-01-01 00:00:01 〜 2038-01-19 03:14:07(UTC)
タイムゾーン変換 しない(入れた値のまま) 保存時にUTCへ、取得時に接続のタイムゾーンへ変換
サイズ 5バイト 4バイト
小数秒 最大6桁(DATETIME(6)) 最大6桁(TIMESTAMP(6))
DEFAULT CURRENT_TIMESTAMP 使える 使える

小数秒を付けると、桁数に応じて最大3バイト増えます。たとえばDATETIME(3)は7バイトです。サイズの差は1バイトしかないため、選ぶ決め手にはなりません。実務で効いてくるのは「タイムゾーン変換」と「範囲」の2点です。

TIMESTAMPはタイムゾーンで見え方が変わる

TIMESTAMPは、接続ごとのタイムゾーン設定(time_zone変数)にしたがって値を変換します。日本時間で入れた値を、UTCの接続で読むとどうなるか試してみましょう。

SET time_zone = '+09:00';   -- 日本時間で接続している想定
INSERT INTO tz_demo VALUES (1, '2026-10-27 09:00:00', '2026-10-27 09:00:00');

SET time_zone = '+00:00';   -- UTCの接続に切り替える
SELECT dt, ts FROM tz_demo WHERE id = 1;
+---------------------+---------------------+
| dt                  | ts                  |
+---------------------+---------------------+
| 2026-10-27 09:00:00 | 2026-10-27 00:00:00 |
+---------------------+---------------------+

DATETIMEは入れた文字どおりの9時のままです。一方、TIMESTAMPは「日本時間の9時=UTCの0時」として読み替えられました。TIMESTAMPは内部でUTCとして持っているため、同じ瞬間を接続先の時刻で表示してくれます。

この性質は、使い方しだいで長所にも短所にもなります。

  • 長所:海外拠点など、タイムゾーンが違う利用者にも「同じ瞬間」を正しく見せられる
  • 短所:アプリサーバーごとにtime_zoneがずれていると、同じ行でも表示される時刻が変わる

なお、MySQL 8.0.19以降は、値の末尾にオフセットを付けて入れることもできます。

-- 「日本時間の9時」と明示して入れる(MySQL 8.0.19以降)
INSERT INTO tz_demo VALUES (2, '2026-10-27 09:00:00+09:00', '2026-10-27 09:00:00+09:00');

この場合、DATETIME列には接続のタイムゾーンに直した値が入ります。UTCの接続なら「00:00:00」です。DATETIMEがタイムゾーン情報を持てるわけではない点に気をつけてください。

2038年問題:TIMESTAMPは範囲外の値を入れられない

TIMESTAMPの上限は「2038-01-19 03:14:07(UTC)」です。内部でUNIX時間(1970年からの秒数)を32ビットの範囲で持つため、これより先の日時は扱えません。日本時間に直すと、2038年1月19日12時14分7秒が上限になります。

SET time_zone = '+00:00';
INSERT INTO tz_demo VALUES (3, '2040-04-01 00:00:00', '2040-04-01 00:00:00');
ERROR 1292 (22007): Incorrect datetime value: '2040-04-01 00:00:00' for column 'ts' at row 1

strictモードではエラーになり、行は入りません。DATETIME列だけなら問題なく入ります。

「作成日時なら過去の値しか入らないから大丈夫」と考えがちですが、注意したい列はほかにもあります。

  1. 契約の満了日や保証期限など、未来の日時を入れる列
  2. 「無期限」を表すために9999-12-31のような遠い日付を入れる列
  3. 2038年を過ぎても使い続ける長寿命のシステムの列

1と2はTIMESTAMPでは今すぐ壊れます。3も、残り10年ほどで確実に問題になると覚えてください。

用途別の選び方

迷ったときは、次の表を目安にしてください。

用途 おすすめ 理由
イベント開催日時・予約日時(現地時刻) DATETIME 「現地の9時」という意味を変換せずに残したい
契約満了日・有効期限 DATETIME 2038年以降の値が入る
作成日時・更新日時(拠点が1つ) どちらでも可 time_zoneを統一していれば差は出にくい
作成日時・更新日時(複数のタイムゾーン) TIMESTAMP、またはUTCで入れるDATETIME 「同じ瞬間」を基準にそろえたい
誕生日・記念日 DATE 時刻がそもそも不要

新しく設計するなら、DATETIMEに統一し、値はアプリ側でUTCにそろえて入れる方針もよく採られます。2038年問題を避けつつ、タイムゾーンの扱いをアプリのコードで一か所にまとめられるためです。

よくある落とし穴

型そのものより、設定や関数との組み合わせでつまずくケースが多くあります。

  • NOW()も接続のタイムゾーンで返る:DATETIME列にDEFAULT CURRENT_TIMESTAMPを付けると、接続ごとのtime_zoneの時刻が入ります。サーバーによって日本時間とUTCが混ざる原因になります。
  • ‘Asia/Tokyo’がエラーになる:名前でタイムゾーンを指定するには、タイムゾーン表を読み込んでおく必要があります(mysql_tzinfo_to_sql)。読み込んでいない環境では’+09:00’のようにオフセットで指定しましょう。
  • 古い記事の自動更新の説明:MySQL 8.0はexplicit_defaults_for_timestampが既定で有効です。「最初のTIMESTAMP列が勝手に自動更新される」動きは、明示しない限り起きません。
  • 小数秒の精度をそろえる:TIMESTAMP(6)にはCURRENT_TIMESTAMP(6)のように、同じ桁数を指定します。
-- 精度をそろえた作成日時・更新日時の定義
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
                                ON UPDATE CURRENT_TIMESTAMP(6)

PostgreSQLでは、timestamp(タイムゾーンなし)とtimestamptz(タイムゾーンあり)の2種類があります。timestamptzはUTCで保存しつつ範囲も広いため、2038年問題はありません。

まとめ

DATETIMEは入れた値をそのまま9999年まで保存し、TIMESTAMPはUTCに変換して2038年1月までを扱います。現地時刻の意味を残したい列や、未来の日付が入る列はDATETIMEが安全です。どちらを使う場合も、接続のtime_zoneをそろえておくことが、時刻のずれを防ぐいちばんの近道になります。

参考リンク

アイキャッチ画像: Photo by insung yoon on Unsplash

タイトルとURLをコピーしました