一言で言うと
エポックタイムとは、コンピュータが時刻を追跡するための一つの方法で、1970年1月1日午前0時(UTC)からの総経過秒数を、ひたすら増え続ける一つの数値で表したものです。
解決する問題
人間と時間の関係って、かなり複雑ですよね。タイムゾーン、サマータイム、MM/DD/YYYYとDD/MM/YYYYみたいなフォーマットの違い。僕らが「2024年10月8日 午後3時」と書いたとしても、東京とトロントじゃ意味が違ってきます。曖昧さでごちゃごちゃです。
一方で、コンピュータは曖昧さが大嫌い。ある瞬間を表現するには、単一で、普遍的で、数学的にシンプルな方法が必要です。「10月8日」で計算しようとするなんて悪夢です。でも、ただの数字で計算するなら?それこそコンピュータが得意とするところです。
この問題を解決するために作られたのが、Unix時間(別名エポックタイム、POSIX時間)です。1970年代のUnixオペレーティングシステムの黎明期、開発者たちは単純明快な時間管理システムを必要としていました。そこで彼らは、ある任意の開始点、つまり「エポック(epoch)」を決めて、そこからひたすら…数を数えることにしたのです。
選ばれたエポックは1970年1月1日 00:00:00 UTCでした。なぜその時だったのか?キリが良く、当時の技術にとって十分に最近の時点だったからです。
その瞬間から、1秒経過するごとに、世界共通のカウンターが増えていきます。だからコンピュータは、「2024年10月8日 午後3時、トロント時間(EDT)」なんてのを解析する必要はなく、ただ1728409200という数字を保存すればいいのです。この数字は、地球上のどこでも同時に、まさにその瞬間を表します。タイムゾーンも、フォーマットも、「午前か午後か?」も関係ありません。ただの数字。これで問題解決です。
内部の仕組み
基本的なコンセプトは死ぬほどシンプルです。でも、テクノロジーの世界ではよくあることですが、悪魔は細部に宿るのです。
エポックと単位
システム全体は、2つのアイデアで成り立っています:
- 開始点(エポック): これは
1970-01-01T00:00:00Zに固定されています。このZはZuluの略で、軍事や航空分野でUTC(協定世界時)を意味する用語です。エポックタイムの世界では、この瞬間は単に0となります。 - 測定単位: 標準の公式な単位は秒です。
なので、タイムスタンプ1は1970-01-01T00:00:01Zを表します。翌日の始まりである1970-01-02T00:00:00Zのタイムスタンプは86400です(1日は60秒 * 60分 * 24時間 = 86,400秒なので)。
// A date far in the future
const humanDate = new Date('2035-10-26T10:00:00Z');
// Its corresponding Epoch timestamp in seconds
const epochTimestamp = 2071754400;
あなたのコンピュータがこのタイムスタンプをローカル時間で表示するとき、裏では変換が行われています。普遍的なUTCタイムスタンプを受け取り、それにシステムのタイムゾーンオフセットを適用して、あなたにとって意味のある形で表示しているのです。しかし、その根底にある数字は、純粋で普遍的なままです。
派生:ミリ秒、マイクロ秒、ナノ秒
時には、1秒よりも速く起こる出来事を測定する必要があります。そのために、システムはより精度の高いバージョンのエポックタイムスタンプを使います。原理は同じですが、単位が変わります。
| 単位 | 同じ瞬間の値の例 | 一般的な桁数 | 典型的なユースケース |
|---|---|---|---|
| 秒 | 1728409200 |
10 | POSIX標準。API、データベース。 |
| ミリ秒 | 1728409200123 |
13 | JavaScript (Date.now())、モダンなAPI。 |
| マイクロ秒 | 1728409200123456 |
16 | 高性能システム、一部のデータベース。 |
| ナノ秒 | 1728409200123456789 |
19 | 科学技術計算、Go言語。 |
これは、タイムスタンプを扱う上でバグの最大の原因です。システムから13桁の数値を受け取って、それを秒として扱ってしまうと、数千年後の日付を計算しようとすることになります。常にドキュメントを確認するか、桁数を見て、自分が何を扱っているのかを把握しましょう。
「2038年問題」
これはコンピュータ界隈の古典的な伝承です。多くの初期システムは、貴重なメモリを節約するために、エポックタイムスタンプを32ビット符号付き整数として保存していました。
「ビット」とは1か0のことです。「32ビット」は1と0を入れるスロットが32個あることを意味します。「符号付き」は、そのうちの1ビットが数値が正か負かを示すために使われるということです。これにより、数値自体には31ビットが残り、最大値2^31 - 1、つまり2,147,483,647まで表現できます。
1970年からの秒数がこの上限に達するとどうなるでしょう?それは2038年1月19日(火曜日)03:14:07 UTCに起こります。その次の瞬間に、整数はオーバーフローします。車の走行距離計が999999から000000にロールオーバーするように、32ビットのタイムスタンプは最も負の値(-2,147,483,648)にラップアラウンドします。これは1901年12月のある日付に相当します。
パッチが適用されていない32ビットシステムでは、これが時間的な大混乱を引き起こします。古い車の組み込みシステム、産業機器、ネットワークルーターなどを想像してみてください。
解決策は?64ビット整数を使うことです。64ビット整数は、約2920億年間はオーバーフローしないほど、気が遠くなるほど大きな数を格納できます。その頃には太陽がとっくに膨張して地球を飲み込んでいるでしょうから、これは恒久的な解決策と言っていいでしょう。ほとんどのモダンなOSや言語はすでにこの移行を済ませています。
うるう秒:厄介な存在
地球の自転は完全に一定ではなく、わずかに遅くなっています。超正確な原子時計を太陽日に同期させるため、国際機関は時々カレンダーに「うるう秒」を追加します。これは、ある1分が61秒になることがある(例:23:59:60)ということです。
では、エポックタイムはこれをどう扱うのでしょう?答えは「扱わない」です。
公式には、POSIX標準はうるう秒を無視します。毎日が正確に86,400秒であると仮定しているのです。うるう秒が発生すると、システムはいくつかの方法で対処しますが、一般的なのは、事実上、直前の1秒を繰り返すというものです。23:59:59のタイムスタンプが2回発生するかもしれません。これにより、秒数の連続した途切れのないカウントは維持されますが、Unixタイムスタンプが現実世界のUTCに常に完全に対応するわけではないことを意味します。99.9%のアプリケーションにとっては、これは問題になりません。しかし、高頻度取引や科学的な測定にとっては、頭の痛い大問題です。
実録:現場の体験談
時間旅行するキャッシュの事件
ある開発チームが、キャッシュシステムを利用した新機能をローンチしていました。パフォーマンス向上のため、データを1時間キャッシュします。ロジックは単純で、expiration_time = current_time() + 3600。彼らはこのコードをサーバー群全体にデプロイしました。
すると突然、奇妙なバグが殺到しました。データがキャッシュからほぼ一瞬で消えてしまうのです。何時間にもわたる必死のデバッグの末、犯人を見つけました。サーバー群の中の一台の新しいサーバーのシステムクロックが間違って設定されており、他のすべてのサーバーより5分遅れていたのです。
ユーザーのリクエストが正常なサーバーに届くと、例えば1678886400(午後12:00)という有効期限でデータがキャッシュされます。その後、同じデータへのリクエストが「遅い」サーバーに届くと、そのサーバーの時計は午前11:55を指しています。キャッシュをチェックすると、有効期限が午後12:00であるのを見て、正しくデータを提供します。しかし、最初のリクエストが遅いサーバーに届いた場合、有効期限を1678882800(自身の時刻である午前11:00に1時間を足して午後12:00)に設定してしまいます。でも、今は午前11:55です。あれ、おかしいな。
もう一度整理しましょう。サーバーAの時刻は12:00です。キャッシュの有効期限を12:00 + 1時間 = 13:00に設定します。サーバーBの時計は遅れていて、11:55だと思っています。サーバーBがキャッシュに書き込む必要がある場合、有効期限を11:55 + 1時間 = 12:55に設定します。さて、もしサーバーAが12:55に期限切れになるアイテムを見つけたら、残り55分あると考えますが、サーバーBは丸々1時間あると考えます。これが不整合を引き起こします。
本当のカオスは、時計が大幅にずれている場合に始まります。もしサーバーBの時計が1時間遅れていたら(実際は12:00なのに11:00だと思っている)、有効期限を11:00 + 1時間 = 12:00に設定します。サーバーAから見れば、この新しいキャッシュアイテムは作成されたその瞬間に期限切れになります。データは事実上、一瞬で消えてしまったのです。
教訓: Unixタイムスタンプは絶対的ですが、それらを生成するシステムクロックはそうでないかもしれません。分散システムにおいて、時計を同期させること(通常はNTP、Network Time Protocolを使用)は、良い習慣であるだけでなく、極めて重要です。
ミリ秒で話すAPI
あるフロントエンド開発者が、ユーザーのアクティビティを表示するダッシュボードを構築していました。バックエンドAPIはlast_loginフィールドを1678886400のようなタイムスタンプで提供していました。開発者はこれを表示するためにJavaScriptのライブラリを使いました:new Date(1678886400)。
結果は奇妙なものでした。すべてのユーザーの最終ログインが「1970年1月20日」と表示されたのです。一体何が起こったのでしょうか?
開発者は1時間ほど、ライブラリや自分のコード、そして月の満ち欠けのせいにしました。ついに、同じAPIの別のエンドポイントを試してみることにしました。すると、今度のタイムスタンプは1678886400123でした。13桁です!その瞬間、ピンときました。JavaScriptのDateオブジェクトのコンストラクタは、タイムスタンプを秒ではなくミリ秒で期待していたのです。
バックエンドは標準的な10桁の秒ベースのタイムスタンプを送っていました。フロントエンドは1,678,886,400をエポックからのミリ秒数として解釈していました。これは1970年のエポック開始からほんの数週間後の日付です。修正は簡単でした:new Date(1678886400 * 1000)。
教訓: タイムスタンプの精度は、必ず、必ず、必ず確認しましょう。ゼロ3つの違いは、今日と1970年の違いです。
よくある間違いと落とし穴
タイムゾーンを忘れる。 Unixタイムスタンプは常に、例外なくUTCです。それを人間が読める日付に変換するとき、あなたのプログラミング言語やツールは、ほとんどの場合、コンピュータのローカルタイムゾーンを使います。これを考慮しないと、大きな混乱を招く可能性があります。
1728409200は一つの正確な瞬間ですが、ニューヨークでは06:00、東京では19:00と表示されます。数字が真実であり、表示は解釈にすぎません。秒とミリ秒を混同する。 これは古典的な「1000倍間違い」エラーです。タイムスタンプを扱う上で、最もよくあるバグです。経験則として、10桁は秒、13桁はミリ秒です。それ以外の桁数を見かけたら、大いに疑ってください。
2038年問題を無視する。 モダンな言語で標準的なウェブアプリを構築しているなら、おそらく大丈夫でしょう。しかし、組み込みIoTデバイスや車のインフォテインメントシステム向けのCコードを書いている場合や、レガシーな32ビットシステムを保守している場合、Y2038バグは非常に現実的な、時を刻む時限爆弾です。
曖昧な文字列からタイムスタンプを生成する。 「March 15, 2025 10:00 PM」のような文字列からタイムスタンプを作成するのは、トラブルの元です。それはあなたのローカルタイムゾーンですか?サーバーのタイムゾーンですか?それともUTC?常にタイムゾーンを意識したオブジェクトからタイムスタンプを生成するか、明示的なUTC文字列(ISO 8601形式など:
2025-03-15T22:00:00Z)を使いましょう。
なぜ知っておくべきか
現代の開発者である以上、エポックタイムを理解しないわけにはいきません。これはコンピュータにおける時間の共通言語です。あらゆるところで遭遇するでしょう:
- API: JSONペイロードでは
createdAt、updatedAt、expires_atといったフィールドで頻繁に使われます。 - JWT:
exp(有効期限)、iat(発行日時)、nbf(有効期間の開始日時)といったクレームはすべて標準的なUnixタイムスタンプです。 - データベース: 時間を単一の整数として保存することは、複雑な
DATETIME型を使うよりも、インデックス作成やストレージの効率が良いことがよくあります。 - ログファイル: 数値のタイムスタンプを使うことで、たとえ異なるタイムゾーンにあっても、何十もの異なるサーバーやサービス間のイベントを簡単に関連付けることができます。
- ファイルシステム: ほとんどのファイルシステムは、ファイルの作成日と更新日をUnixタイムスタンプとして保存しています。
この仕組みを理解すれば、時間に関連する厄介なバグのクラス全体を自信を持ってデバッグできるようになります。タイムゾーンやカレンダーといった人間世界の厄介な問題から抜け出し、コンピュータが時間を考えるように、つまりシンプルで整然とした数字の列として時間を捉えることができるようになります。
さらに深く
- Wikipedia: Unix time — 歴史、仕組み、関連する問題点を網羅した総合的な概要。(英語)
- MDN Web Docs: Date.now() — JavaScriptにおけるミリ秒精度のエポックタイムスタンプの使用法を解説。
- 2038年問題 (Wikipedia) — 32ビット整数のオーバーフローの原因と結果についての詳細な解説。
- RFC 822: Standard for the Format of ARPA Internet Text Messages — 古いですが、テキストベースの日付フォーマットを規定した基礎的な文書で、Unixタイムが回避した複雑さを示しています。(英語)
- A brief history of time zones — そもそもなぜ時間の標準化がそれほど重要だったのかについての背景情報。(英語)