Unix タイムスタンプの仕組みと、2038年問題が今も残っている理由
「Unix タイムスタンプ」または「Epoch 秒」は、プログラミング言語やデータベース、API で時刻を扱う事実上の共通言語です。仕組みは単純なのですが、2038 年問題という有名な落とし穴があり、すでに10年を切った状態です。本記事では Unix タイムスタンプの定義と、2038年問題の現状を整理します。
定義:1970年1月1日 0時 UTC からの秒数
Unix タイムスタンプは:
1970年1月1日 00:00:00 UTC を 0 として、そこからの経過秒数 例:
| 日時(UTC) | Unix タイムスタンプ |
|---|---|
| 1970-01-01 00:00:00 | 0 |
| 1970-01-01 00:00:01 | 1 |
| 2000-01-01 00:00:00 | 946684800 |
| 2024-01-01 00:00:00 | 1704067200 |
| 2038-01-19 03:14:07 | 2147483647 ← 32bit signed int の上限 |
閏秒は無視します。1日は常に 86400 秒として扱う簡易な時刻モデルです。これが厳密な天文時刻と少しズレる原因にもなりますが、システム間の時刻表現としては圧倒的に扱いやすいので広く採用されています。
なぜ 1970年1月1日 が起点か
「Unix が誕生したから」が答えで、それ以上の深い理由はありません。1969 年に Unix の開発が始まり、初期の実装でプログラマにとってキリの良い直近の年(1970年1月1日)を起点に選びました。
「人類の歴史的な時刻」のような根拠は無いので、他の OS や言語は別の起点を使う場合があります:
- Mac OS(古い):1904 年 1 月 1 日
- VMS:1858 年 11 月 17 日
- NTP:1900 年 1 月 1 日
- Windows FILETIME:1601 年 1 月 1 日(100 ナノ秒単位)
- GPS 時刻:1980 年 1 月 6 日
これらの異なる起点を Unix タイムスタンプに変換するときは、起点間のオフセットを加減算します。
2038年問題の正体
Unix タイムスタンプを 32 bit signed integer(time_t の伝統的な型)で保存している実装は、上限が 2^31 - 1 = 2,147,483,647 です。
これに到達する瞬間が:
2038年1月19日 03時14分07秒 UTC この1秒後にカウンタが増えると、32bit signed int では負の数にオーバーフローします:
2147483647 + 1 = -2147483648 (32bit signed int) 負の値は 1970 年より前を表すので、システムが 1901 年12月13日 に戻ってしまうわけです。これが 2038 年問題(Y2038)です。
影響範囲:何が残っているのか
「PC は 64bit になったから関係ない」と思いがちですが、現実には次の領域でまだ問題が残っています。
1. 組み込みシステム
ルーター・産業機器・医療機器・自動車のECU・IoT デバイスなどには、いまだに 32bit CPU + 32bit time_t で動いている製品が大量にあります。これらは:
- 長期使用が前提(10〜20年動く)
- アップデートが困難(フィールドにある機器を一台ずつ更新できない)
- 検証コストが高い(医療・産業用途では認証取り直しが必要)
新規開発でも、「32bit MCU + コスト最適化された C ライブラリ」を選んだ瞬間に 32bit time_t を使っている可能性があります。
2. 既存データベース
int4(32bit)でタイムスタンプを保存しているテーブルは、移行しない限り 2038 年に問題が起きます:
-- ❌ 2038 年で破綻
CREATE TABLE events (
id serial PRIMARY KEY,
ts integer NOT NULL -- Unix timestamp as int32
);
-- ✅ 64bit で問題なし
CREATE TABLE events (
id serial PRIMARY KEY,
ts bigint NOT NULL
);
-- ✅ より良い:タイムスタンプ型を使う
CREATE TABLE events (
id serial PRIMARY KEY,
ts timestamptz NOT NULL
); PostgreSQL の timestamptz は内部的に64bitなので問題ありません。MySQL の TIMESTAMP 型は内部的に4バイトで、MySQL 自体が 2038 年問題を抱えています。DATETIME 型を使うか、明示的に bigint で Unix 秒を保存する必要があります。
3. 古いファイルシステム・アーカイブ形式
ext3 / ext4(古いバージョン)、ZIP、tar の旧形式などは、ファイルのタイムスタンプを 32bit で保存します。古いバックアップを 2038 年以降に展開すると、全ファイルが 1901 年扱いになる可能性があります。
ext4 は 2014 年以降のバージョンで 64bit タイムスタンプ対応、tar も pax 拡張形式で 64bit 対応していますが、すべての場面で最新形式が使われているわけではありません。
4. 通信プロトコル
NTP(時刻同期プロトコル)は 32bit 秒 + 32bit 小数点で 1900 年起点を使っており、2036 年に上限を迎えます。NTPv4 はバージョンビットで対応する仕組みがありますが、移行が完全に終わっているとは言えません。
2038 年問題に備える
新規実装での対策:
time_tを 64bit にする:Linux glibc 2.38 以降は_TIME_BITS=64で64bit化可能- データベースは bigint または timestamp 型を使う
- API レスポンスは ISO 8601 文字列にする:
2026-04-26T12:00:00Zの形式は何 bit でもない
既存システムの監査:
- データベースのカラム型をチェック(int / int4 で時刻を保存していないか)
- ファイル保存形式(古い tar、ZIP のオプション)
- 32bit 環境で動くサービス・スクリプト
ミリ秒タイムスタンプとの違い
JavaScript の Date.now() などは ミリ秒単位の Unix タイムスタンプを返します。秒単位と1000倍違うので、相互変換時に1000をかけ忘れる事故が起きやすい組み合わせです:
const seconds = Math.floor(Date.now() / 1000); // 秒
const ms = seconds * 1000; // ミリ秒 JWT の exp / iat は秒、Date 系 API はミリ秒、というのが多くのライブラリの暗黙のルールです。
ミリ秒の場合、64bit でも 2 億 9000 万年後(西暦 290000000 年あたり)が上限なので、現実的には心配無用です。
起点が違う時刻形式の変換
Unix タイムスタンプと他の時刻形式の変換は、起点のオフセットを足し引きするだけです:
NTP → Unix: ntp_seconds - 2208988800
(1900-01-01 から 1970-01-01 までの秒数)
Windows FILETIME → Unix:
(filetime - 116444736000000000) / 10000000
(1601-01-01 から 1970-01-01 までの 100ns 数)
JavaScript Date → Unix:
Math.floor(date.getTime() / 1000) これらのマジックナンバーを覚える必要はなく、変換が必要になったときにツールで一発変換できれば十分です。
まとめ
- Unix タイムスタンプは 1970-01-01 UTC 起点の経過秒
- 32bit signed int で扱うと 2038 年に上限を迎える
- 組み込み・古い DB・ファイル形式・NTP などまだ問題が残る
- 新規実装は 64bit、可能なら ISO 8601 文字列で扱う
Unix 秒と人間が読める日時の相互変換、または複数のタイムスタンプを一気に確認したいときは、本サイトの Epoch 一括変換ツールが便利です。バッチ処理のログから抜き出した秒数を貼り付けて、ローカル時刻に変換する場面で活用できます。