Engineering · 2026年9月22日

Unixタイムスタンプ変換の解説 — エポック秒、ミリ秒、ISO 8601

Unixタイムスタンプはただの整数に見えますが、単位を取り違えると今日が1970年になります。エポック秒とミリ秒、ISO 8601、タイムゾーン、そしてよくある間違いのための変換チートシート。

ある時刻を保存するシステムは、いつか必ず同じ質問をします。この数値は秒か、ミリ秒か。Unixタイムスタンプは自明に見えます — 1970年1月1日以降に経過した秒数を数える整数 — だからこそ間違って扱われます。単位を取り違えて1000倍ずれた値、1970年になってしまう日付、他の半球のユーザーにしか現れないタイムゾーンのずれ。どれも同じ数少ない誤解から生まれます。

この記事では、Unixタイムスタンプが実際に何を測るのか、秒とミリ秒の違い、ISO 8601との関係、そして推測なしにフォーマットを変換する方法を説明します。

Unixタイムスタンプとは何か

Unixタイムスタンプ(エポック時間とも呼ばれます)は、1970年1月1日 00:00:00 UTC、すなわちUnixエポックからの経過秒数です。タイムゾーン情報を持たない単純な整数で、同じ数値が世界中のどこでも同じ瞬間を表します。

タイムゾーンを持たないため、エポックはマシン同士の交換フォーマットとして自然です。API、ログ、データベース、メッセージキューはタイムスタンプを保存し、人間は日付を読みます。両者の変換こそが、エラーが住んでいる場所です。

タイムスタンプに含まれないものにも注目してください。タイムゾーン、ロケール、カレンダーです。1758537600はひとつの瞬間にすぎません。バンコクの人が21:00と見ようがマドリードの人が14:00と見ようが、それはどう表示するかだけの問題です。

秒とミリ秒 — 最も頻出のエラー

2つの慣習が混在していて、どちらもあちこちに見られます。

  • 秒 — 現在は10桁、例: 1758537600。伝統的なUnix、GoのUnix()、JavaのgetEpochSecond()、Redis、そして多くのデータベースがこの単位を使います。
  • ミリ秒 — 13桁、例: 1758537600000。JavaScriptのDate.now()、JavaのgetTime()、そして多くのブラウザAPIがこの単位を使います。

単位を取り違えると、日付は1970年または2286年へ飛びます。単位を判定する普遍的な規則は存在しないので、確実なのはソースの契約を知ることです。推測するしかないなら、10桁は多くの場合秒、13桁は多くの場合ミリ秒です。ただし推測そのものがエラーであり、解決策ではありません。

規則: 単位を値と一緒に保存してください。カラム名(created_at_ms)、明示的なAPIフィールド、unitプロパティのいずれでも構いません。timeという裸の整数よりはましです。

ISO 8601 — 人が読む側

ISO 8601の文字列はエポック数の対になる存在です。読めて、UTCでソートでき、オフセットを伴っていれば曖昧さもありません。

2026-09-22T14:00:00Z       # 末尾のZで示されるUTC
2026-09-22T14:00:00+02:00  # 同じ瞬間、ベルリンの現地時刻
2026-09-22T14:00:00.123Z   # ミリ秒が保持される

末尾のZと数値オフセットは飾りではありません。どちらもなければ文字列はタイムゾーンを持たず、コードは推測を強いられます。そうして「同じ時刻」が黙って3時間のずれになるのです。

タイムゾーンとUTC

保存と比較はUTCで行い、人向けの表示時だけ、最外層で現地タイムゾーンに変換します。

const now = Date.now();                 // エポックからのミリ秒
const seconds = Math.floor(now / 1000); // エポックからの秒
new Date(seconds * 1000).toISOString(); // "2026-09-22T14:00:00.000Z"

次の2つの規則で、タイムゾーン問題の大半を避けられます。

  • 素のローカル時刻を互いに比較しない。 まず両方を瞬間に直し、それから比較します。
  • ユーザーのタイムゾーンが重要な場面ではオフセットを値と一緒に保管する(予定、スケジュール、課金期間など)。UTCの瞬間にEurope/BerlinのようなIANAゾーンIDを添えるほうが、固定オフセットより優秀です。固定オフセットは夏時間でずれるためです。

変換チートシート

変換 JavaScript Python
ミリ秒 → 日付文字列 new Date(1758537600000).toISOString() datetime.fromtimestamp(1758537600, tz=timezone.utc)
日付文字列 → 秒 Math.floor(Date.parse(s) / 1000) int(dt.replace(tzinfo=timezone.utc).timestamp())
秒 → ミリ秒 seconds * 1000 seconds * 1000
現在時刻 Date.now()(ミリ秒) int(time.time())(秒)

Date.now()がミリ秒を返し、time.time()は秒を返す点に注目してください。単位が普遍な標準ではなく言語ごとの慣習であることの、コンパクトな実例です。

避けるべきエラー

  • 「念のため」1000倍する。 判断は契約に基づき、数値の桁数に基づかないこと。
  • オフセットなしのローカル時刻を保存する。 夏時間がいつか算術を壊します。
  • 文字列を手で分割してパースする。 標準ライブラリのパーサを使いませう。ISO 8601は週番号・基本形式・小数部を持ち、自作コードはそこではずれます。
  • SQLの中に日付フォーマットを持つ。 値は数値またはUTCでデータベースに保持し、表示はロケールとタイムゾーンを制御できるアプリ側で行います。
  • 完全なタイムスタンプを人向けの識別子として使う。 声に出して読むと桁が落ちます。短い参照が必要なら短縮またはハッシュ化します。

試してみる

タイムスタンプコンバーターにエポック値や日付文字列を貼り付けて、秒・ミリ秒・ISO 8601・ローカル時刻を往復してみてください。変換はブラウザ内で行われるため、読みやすくするために機密・非機密を問わずタイムスタンプが端末の外へ出ることはありません。