UNIX timestamp 変換と落とし穴 - 秒/ミリ秒/タイムゾーンの扱いを整理
最終更新日: 2026年4月19日
「ログの時刻が 1713484800 で何時か分からない」「APIの created_at が 13桁なのか10桁なのかで挙動が違う」といった場面は多いです。 UNIX timestamp は 1970年1月1日 00:00:00 UTC からの経過秒という シンプルな仕様ですが、秒/ミリ秒とタイムゾーンの2点で頻繁にバグを生みます。 この記事では変換の基本から、実務で踏みやすい落とし穴までまとめます。
A Unix timestamp is seconds since 1970-01-01 00:00:00 UTC — simple spec, many footguns. Real bugs come from (1) seconds vs milliseconds (10 vs 13 digits), (2) treating UTC values as local time, and (3) 32-bit signed overflow in 2038. This guide covers the correct conversions in JavaScript, Python, and PostgreSQL, plus how to detect which unit a given timestamp is in.
図1: UNIX timestamp → 日時 の流れ (UTC基準)
UNIX timestamp は常にUTC。表示時に任意のタイムゾーンへ変換する。
1. 基本: 秒/ミリ秒の見分け方
桁数で即判別できます。これは人類がUNIX timestampを使い続ける間は変わりません。
| 単位 | 桁数の目安 | 例 (2024-04-19 00:00 UTC) | 典型的な登場場所 |
|---|---|---|---|
| 秒 (s) | 10桁 | 1713484800 | JWT (iat/exp)、PostgreSQL extract(epoch...) |
| ミリ秒 (ms) | 13桁 | 1713484800000 | JS Date.now()、Java System.currentTimeMillis() |
| マイクロ秒 (μs) | 16桁 | 1713484800000000 | Python time.time_ns() // 1000 |
| ナノ秒 (ns) | 19桁 | 1713484800000000000 | Go time.UnixNano() |
秒とミリ秒を取り違えると、だいたい1970年か、遠未来の数万年後が表示されて すぐ気付けますが、ログ集約やCSVエクスポートで数値として扱っていると silent にずれて発覚が遅れます。変換する前に必ず桁数を確認する習慣を。
2. 言語別の変換例
JavaScript / TypeScript
// 現在時刻 → timestamp
const sec = Math.floor(Date.now() / 1000); // 秒 (JWT用)
const ms = Date.now(); // ミリ秒
// timestamp → Date
const d = new Date(1713484800 * 1000); // 秒を受け取ったら *1000
const d2 = new Date(1713484800000); // ミリ秒ならそのまま
// 表示 (JST)
d.toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" });
// => "2024/4/19 9:00:00"
// ISO 8601 (UTC)
d.toISOString(); // "2024-04-19T00:00:00.000Z"
Python
from datetime import datetime, timezone, timedelta
# 現在時刻 → timestamp (秒)
ts = int(datetime.now(timezone.utc).timestamp())
# timestamp (秒) → datetime
dt_utc = datetime.fromtimestamp(1713484800, tz=timezone.utc)
dt_jst = dt_utc.astimezone(timezone(timedelta(hours=9)))
print(dt_jst.isoformat()) # 2024-04-19T09:00:00+09:00
PostgreSQL
-- timestamp (秒) → TIMESTAMPTZ
SELECT to_timestamp(1713484800);
-- => 2024-04-19 09:00:00+09 (セッションのタイムゾーン依存)
-- TIMESTAMPTZ → timestamp (秒)
SELECT extract(epoch FROM now())::bigint;
-- タイムゾーン指定して表示
SELECT to_timestamp(1713484800) AT TIME ZONE 'UTC';
SELECT to_timestamp(1713484800) AT TIME ZONE 'Asia/Tokyo';
シェル (Linux / macOS)
# timestamp → 日時
date -d @1713484800 # GNU date
date -r 1713484800 # BSD date (macOS)
# 日時 → timestamp
date -d "2024-04-19 09:00 +09:00" +%s
# 現在時刻
date +%s # 秒
date +%s%3N # ミリ秒 (GNUのみ)
3. タイムゾーンの落とし穴
UNIX timestamp は 常にUTC基準です。タイムゾーンを「含む」わけではありません。 つまり同じ timestamp は、地球上のどこでも同じ瞬間を指します。表示上 9時間ズレて見えるのは 「表示するタイムゾーンが違う」だけ。
よくある罠:
- 手元のNode.jsとCI/本番でタイムゾーンが違う—
new Date("2024-04-19")のような タイムゾーン非指定の文字列をパースすると、環境のTZ依存の値になる。 ISO 8601 でZ(UTC) もしくは+09:00を必ず付ける。 - DBが
timestamp(without time zone) で格納— PostgreSQLでtimestamp型はタイムゾーン情報を持たない。timestamptzを使うか、アプリケーション側でUTCを明示的に入れる。 - JavaScriptの
toString()— ローカルタイムゾーンで表示されるため、本番ログではtoISOString()を使う。
4. 2038年問題 (Y2K38)
符号付き32bit整数で秒単位のUNIX timestampを扱うと、2038年1月19日 03:14:07 UTC でオーバーフローします。 最大値は 2^31 - 1 = 2147483647。
現代のOS/言語はほぼ64bit化済みですが、組込機器・古いC/C++コード・ MySQLの TIMESTAMP 型 (4バイト)は今でも影響を受けます。 MySQLでは DATETIME (8バイト、9999年まで) を使うのが安全です。
5. 閏秒 (leap second) は?
UNIX timestamp は原則として閏秒を無視します。 23:59:60 が挿入される瞬間はOSが同じ秒値を繰り返すか、時刻を塗りつぶします (Google の Smeared clock など)。通常のWebアプリでここまで気にする必要はありませんが、 金融や分散ロック系で 厳密な単調増加が必要ならCLOCK_MONOTONIC 系のAPIを使います。
6. よくある質問
Q. なぜUnix timestampはUTCなのか?
1970年のUnix誕生時、開発元のベル研はアメリカ東海岸でEST/EDTでしたが、 「世界共通の基準時」としてGMT (後のUTC) が選ばれました。 これによりマシンをまたいだログ同期・ビジネスクロスボーダー処理が一貫します。
Q. JSTで保存したい
UNIX timestamp として保存する以上UTCです。表示だけJSTにしてください。 DBに文字列で "2024-04-19 09:00:00" のように保存すると、 サマータイム地域との連携や集計で破綻します。
Q. JWTの exp は秒、ミリ秒どっち?
秒 (10桁)です (RFC 7519)。13桁の値を入れると、 遠未来となり有効期限切れ検証が効かない・あるいはライブラリがエラーを出します。
English summary
To detect whether a Unix timestamp is in seconds or milliseconds, count digits: 10 digits ≈ seconds (until 2286), 13 digits ≈ milliseconds. In JavaScript, Date takes milliseconds, so always multiply seconds-precision values by 1000. Store as UTC in the database (timestamptzin Postgres) and convert only at the display boundary using the user's timezone. For 32-bit systems, plan the Y2038 migration — 64-bittime_t is already standard on Linux glibc 2.32+.
関連ツール・ガイド / Related tools & guides
- Timestamp ConverterUNIX timestamp ↔ 日時を即座に変換
- JWT Decoderexp/iat の秒値を日時で表示
- JWTの有効期限切れ確認exp の扱い方を実例で解説
- UUID v4とv7の違いv7は48bit timestamp内蔵
参考: RFC 3339 (Date/Time on the Internet) / IANA Time Zone Database