DevToolBox

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 timestamp1713484800= 秒 (10桁)UTC2024-04-19 00:00:00ZJST (+09:00)2024-04-19 09:00:00ISO 86012024-04-19T00:00:00ZDate表示 (ローカル)4/19(金) 09:00

UNIX timestamp は常にUTC。表示時に任意のタイムゾーンへ変換する。

1. 基本: 秒/ミリ秒の見分け方

桁数で即判別できます。これは人類がUNIX timestampを使い続ける間は変わりません。

単位桁数の目安例 (2024-04-19 00:00 UTC)典型的な登場場所
秒 (s)10桁1713484800JWT (iat/exp)、PostgreSQL extract(epoch...)
ミリ秒 (ms)13桁1713484800000JS Date.now()、Java System.currentTimeMillis()
マイクロ秒 (μs)16桁1713484800000000Python time.time_ns() // 1000
ナノ秒 (ns)19桁1713484800000000000Go 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時間ズレて見えるのは 「表示するタイムゾーンが違う」だけ。

よくある罠:

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

参考: RFC 3339 (Date/Time on the Internet) / IANA Time Zone Database