UNIX時間の秒・ミリ秒の見分け方と2038年問題
2026.10.03 更新 · 6分で読めます
UNIX時間は1970年から数えた秒数で、タイムゾーンを持ちません。桁数で秒とミリ秒を見分ける方法、日本時間に変換するときの落とし穴、32ビット整数があふれる2038年問題と確認すべき箇所を解説します。
UNIX時間変換をすぐに使うUNIX時間は1970-01-01T00:00:00Zから数えた秒数で、タイムゾーンを持ちません。現在の日時なら10桁は秒、13桁はミリ秒で、この2つの取り違えがタイムスタンプで最も多い不具合です。2038年問題は秒を符号付き32ビット整数に入れている箇所だけに関係し、その上限は2038-01-19T03:14:07Zです。値をUNIX時間変換に貼り付けると、どの単位として読んだかと、UTC・日本時間の日時をすぐに確認できます。
秒・ミリ秒・マイクロ秒・ナノ秒
同じ瞬間(2026-01-01T00:00:00Z)を4つの単位で書くと次のようになります。
| 単位 | 値 | 桁数 | 見かける場所 |
|---|---|---|---|
| 秒 | 1767225600 | 10 | POSIXのtime()、JWTのexp・iat |
| ミリ秒 | 1767225600000 | 13 | JavaScriptのDate.now() |
| マイクロ秒 | 1767225600000000 | 16 | マイクロ秒精度のログやデータベース |
| ナノ秒 | 1767225600000000000 | 19 | トレーシング、高分解能の時計 |
秒単位の値は2001-09-09T01:46:40Zから2286-11-20T17:46:39Zまで10桁なので、桁数で見分けられます。ツールの自動判定も同じ考え方です。絶対値が10^11より小さければ秒、10^14より小さければミリ秒、10^17より小さければマイクロ秒、それ以上はナノ秒として読み、どの単位と解釈したかをバッジで示します。そのため1767225600123はミリ秒と判定され、2026-01-01T00:00:00.123Zになります。
単位を取り違えると、見分けやすい結果になります。
| 起きたこと | 結果 |
|---|---|
秒の1767225600をミリ秒として読んだ |
1970-01-21T10:53:45.600Z |
ミリ秒の1767225600000を秒として読んだ |
西暦57971年の日付 |
日付が1970年1月になるときは、ミリ秒を受け取る箇所に秒を渡していることがほとんどです。JavaScriptではnew Date(1767225600)が1970年1月になり、new Date(1767225600 * 1000)が意図した日付です。逆に、RFC 7519はJWTのexpとiatを秒と定義しているので、Date.now()ではなくMath.floor(Date.now() / 1000)と比較します。これらのクレームはJWTデコーダーで日時として確認できます。
ナノ秒の値は、JavaScriptではもう1つ注意が必要です。Numberが抜けなく正確に表せる整数(安全な整数)は9,007,199,254,740,991までで、上のナノ秒の値はそれより大きくなります。Number('1767225600000000001')は1767225600000000000になり、最後の桁が失われます。文字列かBigIntで扱ってください。
UNIX時間にタイムゾーンはありません
POSIXは基準時点(Epoch)を1970-01-01 00:00:00 UTCと定義しています。そのため1つのタイムスタンプは世界中どこでも同じ数値で、タイムゾーンが関わるのは人が読む日時に変換するときだけです。
1767225600 = 2026-01-01T00:00:00Z UTC
= 2026-01-01 09:00:00 日本時間(JST、UTC+9)
逆に、2026-01-01 09:00:00をAsia/Tokyoのタイムゾーンで変換すると1767225600に戻ります。
よくある落とし穴は、オフセットのない日時文字列です。ECMA-262は、UTCオフセットがない場合、日付だけの形式はUTC、日付と時刻のある形式はローカル時刻として解釈すると定めています。タイムゾーンがUTC+9のマシンで実行すると次のようになります。
Date.parse('2026-01-01') → 1767225600000 UTCとして解釈
Date.parse('2026-01-01T00:00:00') → 1767193200000 ローカル時刻として解釈(9時間早い時点)
Date.parse('2026-01-01T00:00:00Z') → 1767225600000 UTCを明示
同じコードでも、UTCに設定されたサーバーでは別の数値になります。文字列には必ずZか+09:00のようなオフセットを付けてください。RFC 3339の形式はオフセットを必須としています。
定義から導かれる性質がもう1つあります。POSIXは1日をちょうど86,400秒として数えると定めているため、UNIX時間はうるう秒を数えません。ECMAScriptも同じ方式です。
2038年問題
符号付き32ビット整数の最大値は2,147,483,647です。これを秒として読むと次のようになります。
2147483647 → 2038-01-19T03:14:07Z (日本時間 12:14:07)
-2147483648 → 1901-12-13T20:45:52Z 1秒後に値があふれて戻る先
秒を符号付き32ビットに入れているソフトウェアは、その瞬間に1901年へ戻ったりエラーになったりするおそれがあります。影響を受けるかどうかは、値をどこに入れているかで決まります。
| 保存方法 | 表せる最後の時刻 |
|---|---|
| 符号付き32ビットの秒 | 2038-01-19T03:14:07Z |
| 符号なし32ビットの秒 | 2106-02-07T06:28:15Z |
| 符号付き64ビットの秒 | 1970年から約2,920億年 |
JavaScriptのDate(ミリ秒) |
275760-09-13T00:00:00Z(±8,640,000,000,000,000 ms) |
MySQLのTIMESTAMP列(MySQL 8.4マニュアル) |
‘2038-01-19 03:14:07’ UTC |
問題は2038年になってから始まるわけではありません。上限を超える将来の日時を計算するコードは、今でも影響を受けます。1767225600に365日×15年分の秒数を足すと2240265600(2040-12-28T00:00:00Z)になり、符号付き32ビットに収まりません。有効期限、契約期間、証明書の期限が典型例です。
確認したい箇所は次のとおりです。
- 秒を入れている整数の列: 符号付き32ビットの
INTは上限が同じです。64ビットの整数型を使ってください。 - MySQLの
TIMESTAMP: マニュアルによると範囲は’1970-01-01 00:00:01’ UTCから’2038-01-19 03:14:07’ UTCまで、DATETIMEは’1000-01-01 00:00:00’から’9999-12-31 23:59:59’までです。TIMESTAMPは保存時にUTCへ変換し、DATETIMEは変換しないため、型を変えるとタイムゾーンの扱いも変わります。 - 32ビットのビルドやバイナリ形式: 32ビットの
time_t、時刻フィールドが32ビット固定のファイル形式やプロトコル。
ZEKILO Devで変換する
- UNIX時間変換に数値を貼り付けます。単位は自動で判定され、秒・ミリ秒・マイクロ秒・ナノ秒を自分で指定することもできます。
- 結果はISO 8601(UTC)、RFC 2822、日本語の日時表記、相対時間で表示されます。ブラウザのタイムゾーンとUTC・KST・JSTが並んで表示され、ほかのタイムゾーンはIANA名で選べます。
- 日時からタイムスタンプに変換するときは、ISO 8601、
YYYY-MM-DD HH:mm:ss(選んだタイムゾーンを適用)、RFC 2822の形式で入力します。
2147483647以降の値には、32ビットのtime_tの上限に関する案内が表示されます。計算はすべてブラウザ内で行います。
まとめ
- UNIX時間は1970-01-01T00:00:00Zから数えた秒数です。タイムゾーンを持たず、うるう秒を数えません。
- 現在の日時なら、10桁は秒、13桁はミリ秒、16桁はマイクロ秒、19桁はナノ秒です。
- 日付が1970年1月になったら、秒をミリ秒として読んでいます。
- 保存はUTC基準の64ビット整数か
Z付きの文字列で行い、日本時間への変換は表示のときだけにします。 - 符号付き32ビットの秒は2038-01-19T03:14:07Zで終わります。
INTとMySQLのTIMESTAMP列を確認してください。