結論

Unix タイムスタンプ(エポック秒)は「1970年1月1日 0時0分0秒(UTC)から何秒たったか」を表す数です。日本時間(JST)にするには UTC の日時に9時間を足します。10桁なら秒、13桁ならミリ秒です。

この記事でわかること

  • エポック秒⇔日時(JST・UTC)をその場で変換するツールの使い方
  • 7つの言語・コマンドと MySQL・PostgreSQL で変換した実行結果
  • 秒とミリ秒・時刻帯の取り違え、2038年問題を実際に再現した原文

2026年11月7日更新|Python 3.13・Node.js 22・Java 21・Go 1.24・PHP 8.3・MySQL 8.0・PostgreSQL 16で実行確認

Unix タイムスタンプ変換を読み込み中です。

ツールの使い方:いちばん上に、いまの Unix 時刻(秒)が1秒ごとに表示されます。「いまの値をコピー」でそのまま貼り付けられます。「Unix 時刻 → 日時」の欄に数字を入れると、桁数から秒・ミリ秒・マイクロ秒・ナノ秒を見分け、日本時間・UTC・ISO 8601・このパソコンの時刻・秒・ミリ秒・「いまから◯日前」を表で出します。2026-01-01T09:00:00+09:00 のような日時の文字列を入れても読めます。「日時 → Unix 時刻」では、日時とその日時の時刻帯(日本時間・UTC・このパソコンの時刻)を選ぶと、秒とミリ秒に変わります。2147483647 を超える値には2038年問題の注意が出ます。入力した値はブラウザの中だけで計算し、どこにも送信しません。

  • 時刻帯を書かない日時の文字列は「このパソコンの時刻」として読みます。 日本時間の意味で入れるときは、末尾に +09:00 を付けると、パソコンの設定に左右されません。
  • 「2038年問題の上限」「ミリ秒の例」「0(1970年)」のボタンで、代表的な値をすぐ試せます。

やりたいことごとに、いちばん早い方法を先にまとめます。

やりたいこと いちばん早い方法 詳しい節
Unix タイムスタンプを日時に変換 上のツールに数字を貼る。コードなら JST の時刻帯を指定して変換 変換ツールの使い方
日時を Unix タイムスタンプに変換 ツールの「日時 → Unix 時刻」。コードは時刻帯つきの日時から Python・JavaScript の節
いまのエポック秒を知りたい ツールの1行目。Linux なら date +%s Java・Go・PHP・date コマンド
10桁と13桁の違い 10桁は秒、13桁はミリ秒(1000倍) 秒・ミリ秒の見分け方
1970年や5万年になった 秒とミリ秒の取り違え。1000で割るか掛ける 秒とミリ秒の取り違え
9時間ずれる 時刻帯のない日時を UTC か JST のどちらかに決めつけている JST と UTC の変換
SQL で変換したい MySQL は FROM_UNIXTIME、PostgreSQL は to_timestamp SQL での変換
2038年問題とは 符号つき32ビット整数の上限 2147483647 を超えると負の値に戻る 2038年問題

数字を見てから日時になるまでの流れは、次の3段階です。どこで間違えても、結果は数時間から数万年ずれます。

flowchart TD
    A[数字 1767225600] --> B{桁数は?}
    B -->|10桁| C[秒]
    B -->|13桁| D[ミリ秒]
    B -->|16桁 19桁| E[マイクロ秒 ナノ秒]
    C --> F[UTC の瞬間<br>2026-01-01 00:00:00Z]
    D --> F
    E --> F
    F -->|+9時間| G[日本時間<br>2026-01-01 09:00]
    F -->|そのまま| H[UTC 表示]

確認環境:Ubuntu 24.04.5 LTS の上で、Python 3.13.18(datetime・zoneinfo・sqlite3 の SQLite 3.53.1)、Node.js 22.23.4、OpenJDK 21.0.12.1(java T.java で単一ファイル実行)、Go 1.24.7、PHP 8.3.6(CLI)、Ruby 3.3.6、GNU coreutils 9.4 の date、gcc 13.3.0(glibc 2.39)で変換のコードを実行しました。データベースは、MySQL 8.0.46(Ubuntu の .deb を作業用のフォルダーに展開し、127.0.0.1 だけで起動)と PostgreSQL 16.15(127.0.0.1 だけで起動)で、time_zone と TimeZone を日本時間と UTC に切り替えて試しています。時刻帯の違いは、環境変数 TZ=Asia/Tokyo と TZ=UTC で同じコードを2回動かして比べました。記事内のツールは、Chromium 153.0.8010.12(Playwright)で時刻帯を Asia/Tokyo と UTC にした2通りの画面を開き、表示を確かめています。MySQL の関数の範囲は MySQL 8.0 リファレンスマニュアル、Unix 時刻の定義は POSIX(The Open Group Base Specifications)、うるう秒の今後は BIPM(国際度量衡局)の CGPM 決議で確かめました。Excel の式は、シリアル値の計算を Python で確かめた範囲で書いています。図は mermaid 11.16.0 で描画を確かめています(確認日 2026年11月7日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Unixタイムスタンプ変換|エポック秒⇔日時(JST・UTC)とミリ秒・2038年」

この記事で実際に動かしたもの:Python・JavaScript・Java・Go・PHP・Ruby・date コマンド・C の変換コード、MySQL 8.0 と PostgreSQL 16 と SQLite の SQL、記事内の変換ツール(Chromium)。

Unix タイムスタンプ(エポック秒)とは

この節では、Unix タイムスタンプが何を数えた数なのか、日時の文字列と何が違うのかが分かります。

Unix タイムスタンプは、1970年1月1日 00:00:00 UTC(これを「エポック」と呼びます)からの経過秒数です。 Unix 時間、UNIX time、エポック秒、POSIX 時間とも呼ばれ、どれも同じものを指します。POSIX の定義では「1日をちょうど86400秒として数える」とされていて、うるう秒は数に入りません(うるう秒の節で実物を見ます)。

大事なのは、Unix タイムスタンプそのものには時刻帯(タイムゾーン)が無いことです。1767225600 は、東京でもロンドンでも同じ「瞬間」を指します。それを日本時間で書くと「2026年1月1日 9時0分」、UTC で書くと「2026年1月1日 0時0分」になる、というだけです。

Unix タイムスタンプ(1767225600)

ただの整数。時刻帯を持たない

大小を比べるだけで前後が分かる。引き算で経過秒数

人が見ても日時が分からない

ISO 8601 の日時(2026-01-01T09:00:00+09:00)

人が読める。時刻帯(+09:00 や Z)を書ける

時刻帯を書き忘れると、読む側で意味が変わる

文字列の長さは20〜30文字ほど

代表的な値を、Python の datetime.fromtimestamp() で計算した結果です。10桁の時代は2001年9月9日に始まり、2286年11月20日まで続きます。

Unix 時刻(秒) UTC 日本時間(JST) 意味
0 1970-01-01 00:00:00 1970-01-01 09:00:00 エポック(起点)
1000000000 2001-09-09 01:46:40 2001-09-09 10:46:40 10桁になった瞬間
1767225600 2026-01-01 00:00:00 2026-01-01 09:00:00 この記事の例
1800000000 2027-01-15 08:00:00 2027-01-15 17:00:00 18億秒
2000000000 2033-05-18 03:33:20 2033-05-18 12:33:20 20億秒
2147483647 2038-01-19 03:14:07 2038-01-19 12:14:07 符号つき32ビットの上限
4294967295 2106-02-07 06:28:15 2106-02-07 15:28:15 符号なし32ビットの上限
9999999999 2286-11-20 17:46:39 2286-11-21 02:46:39 10桁の最後

1970年より前の日時は、負の数で表します。-1 は 1969-12-31 23:59:59 UTC(日本時間では 1970-01-01 08:59:59)です。記事内のツールにも -1 を入れて、この結果になることを確かめました。

Unix タイムスタンプは、ログ、API の応答、JWT の有効期限(exp。RFC 7519 では秒の数値と決められています)、Cookie の期限、データベースの列、ファイルの更新時刻など、多くの場所で使われます。次の節では、ツールで変換するときの読み方を見ます。

Unix タイムスタンプ変換ツールの使い方(桁数の判定と時刻帯)

この節では、記事内のツールが数字をどう読むのか、結果の表のどこを見ればよいのかが分かります。

1

数字を貼る

ログや API の応答にある数字を「Unix 時刻 → 日時」の欄に貼ります。入れたとたんに結果が出ます。

秒・ミリ秒どちらでも

2

単位の判定を見る

入力欄の下に「桁数(13桁)から『ミリ秒』として読みました」のように、どの単位として読んだかが出ます。思っていた単位と違えば、元のデータの単位を疑います。

11桁まで秒・14桁までミリ秒

3

日本時間と UTC を両方見る

表の1行目が日本時間、2行目が UTC です。ログの時刻と9時間ずれていたら、ログ側がどちらで書かれているかが分かります。

ISO 8601 は常に UTC(末尾 Z)

4

逆向きは時刻帯を選ぶ

「日時 → Unix 時刻」で日時を入れ、その日時が日本時間か UTC かを選びます。選ぶ時刻帯で結果は32400秒(9時間)変わります。

いちばん間違えやすい所

ツールが桁数で単位を見分ける決まりは次のとおりです(符号と小数点以下は数えません)。

桁数 読み方 入力例 ツールの日本時間の表示
11桁まで 秒 1767225600 2026/01/01(木) 09:00:00.000
12〜14桁 ミリ秒 1767225600123 2026/01/01(木) 09:00:00.123
15〜17桁 マイクロ秒 1767225600123456 2026/01/01(木) 09:00:00 台
18桁以上 ナノ秒 1767225600123456789 2026/01/01(木) 09:00:00 台

マイクロ秒・ナノ秒は、ミリ秒の桁までの確認に使います。 ブラウザの日時はミリ秒までしか持てないため、ミリ秒より細かい桁は正しく扱えません。ミリ秒未満の桁が要る作業は、後の節の Python(time.time_ns())や Go(UnixNano())で扱います。

時刻帯を UTC にしたパソコン(Chromium の時刻帯を UTC にして開いた画面)では、同じ 2026-01-01 09:00:00 を入れると結果が変わりました。

入力 パソコンが日本時間 パソコンが UTC
2026-01-01 09:00:00 1767225600 1767258000
2026-01-01T09:00:00+09:00 1767225600 1767225600
日時 → Unix 時刻(日本時間を選択) 1767225600 1767225600
日時 → Unix 時刻(このパソコンの時刻を選択) 1767225600 1767258000

時刻帯を明示した行だけが、パソコンの設定に左右されませんでした。 サーバーの時刻帯が UTC のことは多いので、コードでも同じことが起きます。次の節では、10桁と13桁がどこから来るのかを見ます。

秒・ミリ秒・マイクロ秒の見分け方(10桁と13桁)

この節では、言語やデータベースごとに「いまの時刻」がどの単位で返ってくるかと、桁数での見分け方が分かります。

いま(2026年11月7日時点)の Unix 時刻は、秒なら10桁、ミリ秒なら13桁です。 手元で同じ瞬間に近いタイミングで取った値を並べると、桁数の違いがはっきり見えます。

10桁秒1793983022(Python の int(time.time()))
13桁ミリ秒1793983030027(JavaScript の Date.now())
19桁ナノ秒1793983022648257598(Python の time.time_ns())
言語・道具 いまの時刻の取り方 単位 手元の出力
Python time.time() 秒(小数つき) 1793983022.648244
JavaScript Date.now() ミリ秒 1793983030027
Java System.currentTimeMillis() ミリ秒 1793983043222
Java Instant.now().getEpochSecond() 秒 1793983043
Go time.Now().Unix() と UnixMilli() 秒とミリ秒 1793983050 と 1793983050010
PHP time() と microtime(true) 秒と秒(小数つき) 1793983059 と 1793983059.949
Ruby Time.now.to_i 秒 1793983084
Linux date +%s と date +%s%3N 秒とミリ秒 1793983059 と 1793983059958
PostgreSQL extract(epoch from now()) 秒(numeric 型・小数つき) 型は numeric

JavaScript と Java の標準はミリ秒、それ以外の多くは秒です。 JavaScript で作った値を Python や PHP に渡すとき(またはその逆)に、取り違えが起きます。見分け方は単純です。

  1. 110桁 → 秒2001年9月〜2286年11月の日時
  2. 213桁 → ミリ秒1000で割ると秒。JavaScript・Java から来た値
  3. 316桁 → マイクロ秒データベースや分散トレーシングの記録
  4. 419桁 → ナノ秒Go の UnixNano、Python の time_ns
  5. 5小数点つき → 秒(小数部はそれ以下)Python の time.time()、PHP の microtime(true)

次の節では、単位を取り違えたときに何が表示されるかを、実際のエラーと出力で見ます。

秒とミリ秒の取り違え:1970年や5万年が出る原因

この節では、秒とミリ秒を取り違えたときに各言語・データベースが返す結果と、その直し方が分かります。

秒をミリ秒として読むと「1970年1月21日」、ミリ秒を秒として読むと「西暦57971年」になります。 どちらも、1000倍の取り違えです。同じ2つの値(秒の 1767225600 と、ミリ秒の 1767225600000)を、単位を逆にして渡した結果です。

言語・道具 取り違えたコード 結果(原文)
JavaScript new Date(1767225600)(秒を渡した) 1970-01-21T10:53:45.600Z
Java Instant.ofEpochMilli(1767225600L) 1970-01-21T10:53:45.600Z
Java new java.util.Date(1767225600L) Wed Jan 21 19:53:45 JST 1970
Python datetime.fromtimestamp(1767225600000, JST) ValueError: year 57971 is out of range
PHP date('Y-m-d H:i:s', 1767225600123) 57971-02-25 09:02:03
date コマンド date -d @1767225600123 Thu Feb 25 09:02:03 JST 57971
PostgreSQL to_timestamp(1767225600000) 57971-02-25 09:00:00+09
MySQL FROM_UNIXTIME(1767225600000) NULL
SQLite datetime(1767225600123,'unixepoch') NULL(Python では None)

1エラーにならない言語がある

JavaScript・Java・PHP・PostgreSQL は、間違った日時をそのまま返した。画面に出るまで気づけない。

2MySQL と SQLite は NULL

エラーにならず NULL になる。集計で NULL が増えたら、単位を疑う。

3Python だけが止まった

年が 9999 を超えるため ValueError になった。原因が分かりやすい。

直し方は、受け取る側の単位に合わせて1000で割るか掛けることです。 ミリ秒のまま小数点以下を残したいときは、割り算の結果を小数のまま渡します。

# ミリ秒 → 秒(Python)
datetime.fromtimestamp(1767225600123 / 1000, JST)
# 2026-01-01 09:00:00.123000+09:00
// 秒 → ミリ秒(JavaScript)
new Date(1767225600 * 1000).toISOString();
// '2026-01-01T00:00:00.000Z'

MySQL は FROM_UNIXTIME(1767225600123/1000) で 2026-01-01 09:00:00.1230、PostgreSQL は to_timestamp(1767225600123/1000.0) で 2026-01-01 09:00:00.123+09 になりました。PostgreSQL で整数どうしの割り算(/1000)にすると小数点以下が切り捨てられるので、1000.0 で割ります。次の節では、もう1つの定番の取り違え「9時間ずれ」を見ます。

日本時間(JST)と UTC の変換:9時間ずれる原因

この節では、エポック秒と日時を変換するときに9時間ずれる仕組みと、時刻帯を明示して防ぐ書き方が分かります。

9時間ずれの原因は、ほとんどが「時刻帯の書いていない日時」を、書いた人と読む側で違う時刻帯として扱ったことです。 日本時間は UTC より9時間進んでいる(UTC+9)ので、取り違えると 32400 秒ずれます。日本には夏時間(サマータイム)が無いため、ずれは1年中9時間ちょうどです。

同じ「2026-01-01 09:00:00」を、時刻帯を書かずに秒へ変換した結果です。環境変数 TZ だけを変えて、同じコードを動かしました。

言語・道具 時刻帯を書かない変換 TZ=Asia/Tokyo TZ=UTC
Python datetime(2026,1,1,9).timestamp() 1767225600 1767258000
JavaScript Date.parse("2026-01-01T09:00:00") 1767225600000 1767258000000
PHP strtotime('2026-01-01 09:00:00') 1767225600 1767258000
date コマンド date -d "2026-01-01 09:00:00" +%s 1767225600 1767258000
MySQL UNIX_TIMESTAMP('2026-01-01 09:00:00') 1767225600 1767258000

MySQL は環境変数ではなく、セッションの time_zone(+09:00 と +00:00)を切り替えた結果です。MySQL のマニュアルにも「UNIX_TIMESTAMP() は引数をセッションの時刻帯の日時とみなす」と書かれています。

言語によっては、TZ に関係なく「UTC とみなす」関数もあります。こちらは、日本時間の意味で書いた日時を渡すと、逆向きに9時間ずれます。

言語 UTC とみなす書き方 結果 日本時間として読む書き方
Go time.Parse(layout, s) 1767258000 time.ParseInLocation(layout, s, jst) → 1767225600
Java ldt.toEpochSecond(ZoneOffset.UTC) 1767258000 ldt.atZone(ZoneId.of("Asia/Tokyo")) → 1767225600
JavaScript Date.parse("2026-01-01")(日付だけ) 1767225600000(UTC の0時) 末尾に +09:00 を書く
PostgreSQL extract(epoch from timestamp '...') 1767258000 timestamptz '... +09' → 1767225600

JavaScript は、日付だけの文字列を UTC、日付と時刻の文字列をパソコンの時刻帯として読みました。 TZ=UTC で動かすと、"2026-01-01" は 1767225600000、"2026-01-01T09:00:00" は 1767258000000 で、同じ書式に見える2つの扱いが違います。

日時を文字列でやり取りする

避ける2026-01-01 09:00:00 のように時刻帯を書かない
→
代わりに2026-01-01T09:00:00+09:00 か 2026-01-01T00:00:00Z

ISO 8601(RFC 3339)の形で時刻帯まで書けば、どの言語・どのサーバーでも同じ瞬間になる

エポック秒を日時にする

避けるサーバーの時刻帯(TZ)任せで変換する
→
代わりにAsia/Tokyo などの時刻帯を引数で渡す

手元とコンテナで TZ が違うと、同じコードで9時間ずれる

9時間を足して直す

避ける表示がずれたので + 32400 を足す
→
代わりにどこで時刻帯が抜けたかを探して、そこで明示する

足した値は「UTC の瞬間」として正しくなくなり、別の場所で二重にずれる

原因ごとの直し方(Docker・JDBC の設定など)は、関連記事「日時が9時間ずれる原因と直し方|Java・JS・MySQL・PG・Docker」で扱っています。次の節からは、言語ごとの変換のコードと実行結果です。

Python でエポック秒と日時を変換する(datetime・zoneinfo)

この節では、Python でエポック秒を日本時間・UTC の日時にする方法と、日時をエポック秒に戻す方法が、実行結果つきで分かります。

Python では datetime.fromtimestamp(秒, 時刻帯) で日時に、日時.timestamp() で秒に変換します。 時刻帯は標準ライブラリの zoneinfo で ZoneInfo("Asia/Tokyo") と書きます(Python 3.9 から)。

import time, datetime as dt
from zoneinfo import ZoneInfo
JST = ZoneInfo("Asia/Tokyo")

print(int(time.time()))      # いまの秒
print(time.time_ns())        # いまのナノ秒
t = 1767225600
print(dt.datetime.fromtimestamp(t, dt.timezone.utc))
print(dt.datetime.fromtimestamp(t, JST))
print(dt.datetime.fromtimestamp(t, dt.timezone.utc).isoformat().replace("+00:00", "Z"))
d = dt.datetime(2026, 1, 1, 9, 0, 0, tzinfo=JST)
print(int(d.timestamp()))
print(int(dt.datetime.fromisoformat("2026-01-01T09:00:00+09:00").timestamp()))
print(dt.datetime.fromtimestamp(1767225600123 / 1000, JST))
1793983022
1793983022648257598
2026-01-01 00:00:00+00:00
2026-01-01 09:00:00+09:00
2026-01-01T00:00:00Z
1767225600
1767225600
2026-01-01 09:00:00.123000+09:00

datetime.utcfromtimestamp() は、Python 3.12 から非推奨です。 手元の 3.13 で呼ぶと、次の警告が出ました。結果に時刻帯が付かない(naive な日時になる)ので、後で日本時間として扱われる事故のもとになります。

DeprecationWarning: datetime.datetime.utcfromtimestamp() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.fromtimestamp(timestamp, datetime.UTC).

代わりに datetime.fromtimestamp(t, datetime.UTC)(または datetime.timezone.utc)を使います。また、9999-12-31 23:59:59 UTC(253402300799)を日本時間にしようとすると、年が 10000 になるため OverflowError: date value out of range になりました。上限近くの値を扱うときは UTC のまま持ちます。月末の計算や時刻帯の扱い全体は、関連記事「Python datetimeの使い方|タイムゾーン・月末計算を実行結果で」で扱っています。次は JavaScript です。

JavaScript でエポック秒と日時を変換する(Date・Intl)

この節では、JavaScript の Date でエポック秒を扱うときの「ミリ秒」の決まりと、日本時間で表示する書き方が分かります。

JavaScript の Date は、内部ではエポックからのミリ秒を持っています。 秒の値は1000倍してから new Date() に渡し、日本時間の表示は toLocaleString か Intl.DateTimeFormat に timeZone: "Asia/Tokyo" を渡します。

const t = 1767225600;                 // 秒
const d = new Date(t * 1000);
console.log(Math.floor(Date.now() / 1000));
console.log(d.toISOString());
console.log(d.toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" }));
console.log(new Intl.DateTimeFormat("ja-JP", {
  timeZone: "Asia/Tokyo", dateStyle: "full", timeStyle: "long"
}).format(d));
console.log(Date.parse("2026-01-01T09:00:00+09:00") / 1000);
console.log(d.toString());
1793983030
2026-01-01T00:00:00.000Z
2026/1/1 9:00:00
2026年1月1日木曜日 9:00:00 JST
1767225600
Thu Jan 01 2026 09:00:00 GMT+0900 (Japan Standard Time)

toString() の結果だけは、実行した環境の時刻帯で変わります。TZ=UTC で動かすと Thu Jan 01 2026 00:00:00 GMT+0000 (Coordinated Universal Time) でした。timeZone を指定した2行は、どちらの環境でも同じ結果です。

Date が表せる範囲はエポックの前後 8.64×1015 ミリ秒(約27万年)までです。new Date(8.64e15) は +275760-09-13T00:00:00.000Z、1ミリ秒でも超えると Invalid Date になり、toISOString() は RangeError: Invalid time value を投げました。秒とミリ秒を取り違えて 1000 倍の、さらに 1000 倍を渡したときなどに、このエラーに出会います。新しい日時の API の Temporal は、関連記事「JavaScript Temporal 使い方|Dateからの移行早見表と実行結果」で扱っています。次は Java・Go・PHP・date コマンドです。

Java・Go・PHP・date コマンドでの Unix 時刻の変換

この節では、Java の Instant、Go の time.Unix、PHP の date、Linux の date コマンドでの変換方法と、実行結果が分かります。

Java(java.time の Instant)

Java 8 以降は Instant.ofEpochSecond()(秒)と Instant.ofEpochMilli()(ミリ秒)で受け取り、atZone() で日本時間にします。

Instant i = Instant.ofEpochSecond(1767225600L);
System.out.println(i);
System.out.println(i.atZone(ZoneId.of("Asia/Tokyo")));
System.out.println(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")
    .withZone(ZoneId.of("Asia/Tokyo")).format(i));
System.out.println(Instant.ofEpochMilli(1767225600123L));
System.out.println(LocalDateTime.of(2026, 1, 1, 9, 0)
    .atZone(ZoneId.of("Asia/Tokyo")).toEpochSecond());
2026-01-01T00:00:00Z
2026-01-01T09:00+09:00[Asia/Tokyo]
2026/01/01 09:00:00
2026-01-01T00:00:00.123Z
1767225600

Go(time.Unix)

t := time.Unix(1767225600, 0)
jst, _ := time.LoadLocation("Asia/Tokyo")
fmt.Println(t.UTC())
fmt.Println(t.In(jst).Format("2006-01-02 15:04:05 MST"))
fmt.Println(t.UTC().Format(time.RFC3339))
fmt.Println(time.UnixMilli(1767225600123).In(jst))
p, _ := time.ParseInLocation("2006-01-02 15:04:05", "2026-01-01 09:00:00", jst)
fmt.Println(p.Unix())
2026-01-01 00:00:00 +0000 UTC
2026-01-01 09:00:00 JST
2026-01-01T00:00:00Z
2026-01-01 09:00:00.123 +0900 JST
1767225600

Go の time.LoadLocation は、OS の時刻帯データ(tzdata)を読みます。tzdata の入っていない小さなコンテナ用イメージでは失敗することがあり、その場合は Go の time/tzdata パッケージを読み込む方法が公式に用意されています。

PHP(date・DateTimeImmutable)

<?php
date_default_timezone_set('Asia/Tokyo');
echo date('Y-m-d H:i:s', 1767225600), "\n";
echo gmdate('Y-m-d\TH:i:s\Z', 1767225600), "\n";
echo strtotime('2026-01-01 09:00:00'), "\n";
echo (new DateTimeImmutable('@1767225600'))
    ->setTimezone(new DateTimeZone('Asia/Tokyo'))->format(DATE_ATOM), "\n";
2026-01-01 09:00:00
2026-01-01T00:00:00Z
1767225600
2026-01-01T09:00:00+09:00

new DateTimeImmutable('@1767225600') の @ は「Unix 時刻として読む」の印で、この形では時刻帯が UTC になります。日本時間で表示するには setTimezone() を続けます。手元の PHP は PHP_INT_SIZE が 8(64ビット整数)で、2038年以降の値も扱えました。

Linux の date コマンド

$ date +%s
1793983059
$ date -d @1767225600
Thu Jan  1 09:00:00 JST 2026
$ date -u -d @1767225600 +%FT%TZ
2026-01-01T00:00:00Z
$ date -d "2026-01-01 09:00:00 +0900" +%s
1767225600
$ date -d @99999999999999999
date: time '99999999999999999' is out of range

この -d @秒 の書き方は GNU 版(Linux)のものです。macOS の date は BSD 系でオプションが違い、同じ書き方では動きません。書式の一覧や日付の計算は、関連記事「dateコマンドのフォーマット一覧と日付計算|月末・macOS差異もコピペで」で扱っています。Ruby では Time.at(1767225600) が 2026-01-01 09:00:00 +0900(TZ=Asia/Tokyo)、.utc を付けると 2026-01-01 00:00:00 UTC でした。次はデータベースです。

SQL での Unix 時刻の変換(MySQL・PostgreSQL・SQLite)

この節では、MySQL・PostgreSQL・SQLite でエポック秒と日時を変換する関数と、セッションの時刻帯で結果が変わる様子が分かります。

データベース 秒 → 日時 日時 → 秒 時刻帯の決まり方
MySQL 8.0 FROM_UNIXTIME(秒) UNIX_TIMESTAMP('日時') セッションの time_zone
PostgreSQL 16 to_timestamp(秒) extract(epoch from 日時) セッションの TimeZone
SQLite datetime(秒,'unixepoch') unixepoch('日時') 結果は常に UTC

MySQL

SELECT FROM_UNIXTIME(1767225600) AS jst,
       UNIX_TIMESTAMP('2026-01-01 09:00:00') AS secs,
       FROM_UNIXTIME(1767225600, '%Y/%m/%d %H:%i:%s') AS fmt;
SET time_zone = '+00:00';
SELECT FROM_UNIXTIME(1767225600) AS utc;
+---------------------+------------+---------------------+
| jst                 | secs       | fmt                 |
+---------------------+------------+---------------------+
| 2026-01-01 09:00:00 | 1767225600 | 2026/01/01 09:00:00 |
+---------------------+------------+---------------------+
+---------------------+
| utc                 |
+---------------------+
| 2026-01-01 00:00:00 |
+---------------------+

MySQL の TIMESTAMP 型は内部で UTC に直して保存し、DATETIME 型は書いたままの日時を保存します。 ts を TIMESTAMP 型、dt を DATETIME 型にした表へ、日本時間のセッションで同じ '2026-01-01 09:00:00' を入れ、セッションを UTC に変えて読むと、TIMESTAMP だけが9時間戻りました。

+---------------------+---------------------+
| ts                  | dt                  |
+---------------------+---------------------+
| 2026-01-01 00:00:00 | 2026-01-01 09:00:00 |
+---------------------+---------------------+

FROM_UNIXTIME の上限は、MySQL 8.0.28 以降の64ビット環境で 32536771199(3001-01-18 23:59:59 UTC)です。手元でも FROM_UNIXTIME(32536771199) は 3001-01-19 08:59:59(日本時間)、1秒多い 32536771200 は NULL でした(マニュアルの文面は「上限を超えると 0 を返す」ですが、手元の 8.0.46 の表示は NULL でした)。2つの型の違いは、関連記事「MySQL DATETIMEとTIMESTAMPの違い|2038年・タイムゾーン」で扱っています。

PostgreSQL

SELECT to_timestamp(1767225600) AS jst,
       to_timestamp(1767225600123 / 1000.0) AS ms,
       extract(epoch from timestamptz '2026-01-01 09:00:00+09') AS secs;
          jst           |             ms             |       secs
------------------------+----------------------------+-------------------
 2026-01-01 09:00:00+09 | 2026-01-01 09:00:00.123+09 | 1767225600.000000

to_timestamp の結果は timestamptz(時刻帯つき)型で、セッションの TimeZone で表示されます。SET timezone='UTC' にすると 2026-01-01 00:00:00+00 でした。extract(epoch ...) は numeric 型を返すので、整数が要るときは ::bigint を付けます。

SQLite

SELECT datetime(1767225600, 'unixepoch');               -- 2026-01-01 00:00:00
SELECT datetime(1767225600, 'unixepoch', '+9 hours');   -- 2026-01-01 09:00:00
SELECT unixepoch('2026-01-01 09:00:00+09:00');          -- 1767225600
SELECT datetime(1767225600123 / 1000.0, 'unixepoch', 'subsec');
                                                        -- 2026-01-01 00:00:00.123

SQLite は時刻帯のデータを持たないため、日本時間で表示するときは '+9 hours' を付けます('localtime' を使うと、実行したパソコンの時刻帯になります)。次の節では、2038年問題を実際に起こしてみます。

2038年問題とは:32ビットの溢れを実際に再現

この節では、2038年問題が何年何時何分に、なぜ起きるのかと、手元で再現した結果、確かめておく点が分かります。

2038年問題は、Unix 時刻を符号つき32ビット整数で持っている仕組みが、2038年1月19日 03:14:07 UTC(日本時間 12:14:07)の1秒後に値を表せなくなる問題です。 符号つき32ビット整数の最大値は 2147483647 で、1を足すと -2147483648 に戻り、日時は1901年12月13日になります。

#include <stdio.h>
#include <stdint.h>
#include <time.h>
int main(void) {
  printf("sizeof(time_t) = %zu\n", sizeof(time_t));
  int32_t t = INT32_MAX;
  for (int i = 0; i < 2; i++) {
    time_t tt = (time_t)t;
    char buf[64];
    strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S UTC", gmtime(&tt));
    printf("int32 %11d -> %s\n", t, buf);
    t = (int32_t)((uint32_t)t + 1u);
  }
  return 0;
}
sizeof(time_t) = 8
int32  2147483647 -> 2038-01-19 03:14:07 UTC
int32 -2147483648 -> 1901-12-13 20:45:52 UTC

64ビットの Linux(手元の x86_64)では time_t が8バイトなので、OS の時刻そのものは溢れません。問題になるのは、32ビットの変数・列・ファイル形式に時刻を入れている所です。各言語でも同じ溢れを再現しました。

場所 やったこと 結果(原文)
Java int の 2147483647 に1を足して Instant.ofEpochSecond -2147483648 -> 1901-12-13T20:45:52Z
Go int32 の 2147483647 に1を足して time.Unix -2147483648 1901-12-13 20:45:52 +0000 UTC
JavaScript (2147483647 + 1) | 0(ビット演算は32ビット) -2147483648
Python struct.pack('<i', 2147483648) struct.error: 'i' format requires -2147483648 <= number <= 2147483647
MySQL TIMESTAMP 列に '2038-01-19 12:14:08'(日本時間)を入れる ERROR 1292 (22007): Incorrect datetime value
PostgreSQL to_timestamp(2147483648) 2038-01-19 03:14:08+00(問題なし)

MySQL の TIMESTAMP 型は、2038-01-19 03:14:07 UTC までしか入りません。 厳格モード(STRICT_TRANS_TABLES)で1秒後の日時を入れると、次のエラーになりました。DATETIME 型は 9999 年まで入ります。

ERROR 1292 (22007) at line 8: Incorrect datetime value: '2038-01-19 12:14:08' for column 'ts' at row 1

JavaScript の | 0 や >> 0 で小数を切り捨てるくせがあるコードは、秒の値でも2038年を過ぎると負の数になります。Math.floor() か Math.trunc() で切り捨てると、この問題は起きません。

32ビットの Linux で動かす C のプログラムは、glibc のマクロ _TIME_BITS=64 で time_t を64ビットにできます。手元の glibc 2.39 のヘッダーで、このマクロは _FILE_OFFSET_BITS=64 と組で使う決まりになっていて、片方だけを付けてコンパイルすると次のエラーで止まりました。

/usr/include/features-time64.h:26:5: error: #error "_TIME_BITS=64 is allowed only with _FILE_OFFSET_BITS=64"

2038年問題に備えて、自分のシステムで確かめておく点です。

  • 1DB の列に MySQL の TIMESTAMP 型を使っていないか有効期限・予約日など未来の日時は特に。DATETIME か BIGINT へ
  • 2Unix 時刻を int(32ビット)で持っていないかJava の int、C の int32_t、Go の int32。long・int64 へ
  • 3ビット演算で切り捨てていないかJavaScript の | 0 や >> 0
  • 4ファイル形式・通信の形式に4バイトの時刻欄がないか独自のバイナリ形式、組み込み機器との通信
  • 52038年以降の日時でテストしたか2147483648 を入れて画面・DB・API を一通り通す

「2038年はまだ先」と思っても、住宅ローンや保険の満期、証明書・ライセンスの期限のような未来の日時を扱う処理では、いますでに2038年を超える値が入ります。ほかの日付処理の落とし穴は、関連記事「日付処理の落とし穴8選|2038年問題・うるう秒・うるう年2100年」で扱っています。次の節では、Unix 時刻とうるう秒の関係を見ます。

うるう秒と Unix タイムスタンプ

この節では、Unix 時刻がうるう秒をどう扱うのかと、うるう秒の日時を入れたときの各言語の反応が分かります。

Unix 時刻は、1日を必ず86400秒として数えるため、うるう秒(23:59:60)を表す値がありません。 直近のうるう秒は 2016年12月31日 23:59:60 UTC(日本時間 2017年1月1日 8:59:60)に入りましたが、Unix 時刻では、その前後がちょうど1秒差です。

$ date -u -d "2016-12-31 23:59:59" +%s
1483228799
$ date -u -d "2017-01-01 00:00:00" +%s
1483228800
$ date -u -d "2016-12-31 23:59:60" +%s
date: invalid date '2016-12-31 23:59:60'

Python の datetime(2016, 12, 31, 23, 59, 60) は ValueError: second must be in 0..59、JavaScript の Date.parse("2016-12-31T23:59:60Z") は NaN でした。実際の時計(UTC)では2秒たっていても、Unix 時刻の差は1秒です。そのため、うるう秒をまたいだ「正確な経過秒数」が要る計測には、Unix 時刻の引き算は向きません。うるう秒の瞬間の OS の動き(同じ秒を2回繰り返す、前後の時間に少しずつ散らす等)は、OS や NTP サーバーの設定で変わります。

うるう秒は今後なくなる方向です。2022年の第27回国際度量衡総会(CGPM)の決議4で、UT1 と UTC の差の上限を2035年までに引き上げる(=うるう秒を入れずに済むようにする)ことが決まりました(BIPM の公表文)。記事内のツールも、うるう秒を Unix 時刻に含めない決まりで表示します。次の節では、ここまでのつまずきを原文つきで一覧にします。

よくあるエラー・つまずき(原文と直し方)

この節では、Unix タイムスタンプの変換で出会うエラーや、おかしな表示の原因と直し方が、一覧で分かります。

症状・エラー(原文) 原因 直し方
1970-01-21 ごろの日時になる 秒をミリ秒として渡した(JavaScript・Java) 1000を掛けてから渡す
ValueError: year 57971 is out of range ミリ秒を秒として渡した(Python) 1000で割る
57971-02-25 のような日時 ミリ秒を秒として渡した(PHP・date・PostgreSQL) 1000で割る
FROM_UNIXTIME の結果が NULL ミリ秒を渡した、または上限 32536771199 を超えた /1000 する。範囲を確かめる
ちょうど9時間ずれる 時刻帯のない日時を、UTC と JST で取り違えた 時刻帯を引数・文字列で明示する
手元では合うのにサーバーで9時間ずれる サーバー・コンテナの TZ が UTC TZ に頼らず Asia/Tokyo を指定する
RangeError: Invalid time value Date の範囲(±8.64e15 ミリ秒)を超えた、または読めない文字列 単位と入力の形を確かめる
ERROR 1292 (22007): Incorrect datetime value MySQL の TIMESTAMP 列に2038年1月19日以降を入れた DATETIME 型か BIGINT 型にする
DeprecationWarning: utcfromtimestamp() is deprecated Python 3.12 以降で非推奨の関数を使った fromtimestamp(t, datetime.UTC)
OverflowError: date value out of range 9999年の終わり近くを日本時間にした(Python) UTC のまま扱う
date: invalid date '... 23:59:60' うるう秒は Unix 時刻で表せない 23:59:59 か翌日の 0:00:00 として扱う

表示がおかしいときは、まず値を記事内のツールに貼り、単位の判定と、日本時間・UTC の2行を見比べると、どの取り違えかがすぐ分かります。次の節では、保存ややり取りにどの形を選ぶかを判断表にまとめます。

判断表:エポック秒・ミリ秒・ISO 8601 のどれで保存・やり取りするか

この節では、データベースの列や API で日時を持つときに、どの形を選ぶとよいかが分かります。

場面 向いている形 理由
人も読む API の応答・設定ファイル ISO 8601(2026-01-01T00:00:00Z) 読める。時刻帯を書ける。RFC 3339 の形はどの言語でも読める
JWT の exp・iat 秒(整数) RFC 7519 で秒の数値と決められている
ブラウザ・Node.js の中だけで使う ミリ秒 Date.now() と同じ単位で変換が要らない
ログ・イベントの大量の記録 秒かミリ秒の整数(単位を名前に書く) 小さく、並べ替え・引き算が速い。created_at_ms のように単位を列名に
MySQL の列 DATETIME(UTC で入れる)か BIGINT TIMESTAMP は2038年まで。DATETIME は時刻帯を持たないので UTC で揃える
PostgreSQL の列 timestamptz 瞬間として保存され、表示だけセッションの時刻帯になる
他社のシステムとのやり取り 相手の仕様に合わせ、単位と時刻帯を文書に書く 秒・ミリ秒の取り違えはここで起きやすい

どの形でも、「単位」と「時刻帯」を名前か文書のどちらかに必ず書くことが、取り違えを防ぐいちばんの方法です。 「保存は UTC、表示のときだけ日本時間」と決めておくと、どの形を選んでも9時間ずれが起きにくくなります。最後に、よくある質問をまとめます。

よくある質問

この節では、Unix タイムスタンプの変換でよく調べられる疑問の答えが分かります。

Q. Unix 時間・エポック秒・POSIX 時間・UNIX タイムスタンプは違うものですか?
同じものです。どれも 1970-01-01 00:00:00 UTC からの経過秒数を指します。ミリ秒で数えたものは「エポックミリ秒」と呼び分けることがあります。

Q. 10桁と13桁、どちらが正しいのですか?
どちらも正しく、単位が違います。10桁は秒、13桁はミリ秒です。受け取る関数がどちらの単位を前提にしているかを、ドキュメントで確かめます。

Q. Unix タイムスタンプを日本時間に変換するには?
UTC の日時に9時間を足した日時が日本時間です。コードでは「+32400」を足すのではなく、Python なら fromtimestamp(t, ZoneInfo("Asia/Tokyo"))、JavaScript なら toLocaleString("ja-JP", {timeZone: "Asia/Tokyo"}) のように時刻帯を指定します。

Q. Excel で Unix タイムスタンプを日時にするには?
Excel の日付は 1900年1月1日を 1 とするシリアル値で、1970年1月1日は 25569 です。セル A1 の秒を日本時間にするなら =A1/86400+25569+9/24 とし、セルの表示形式を日時にします。1767225600 なら 46023.375(2026年1月1日 9時)になることを Python の計算で確かめました。ミリ秒なら先に1000で割ります。

Q. マイナスの Unix タイムスタンプはありますか?
あります。1970年より前の日時は負の数です。-1 は 1969-12-31 23:59:59 UTC です。ただし、符号なしの整数で持つ仕組みや、負の値を受け付けない関数もあります。

Q. 2038年問題は、いまのパソコンやスマホでも起きますか?
64ビットの OS と、時刻を64ビットで持つプログラムでは起きません。起きるのは、時刻を32ビットで持つ部分(MySQL の TIMESTAMP 型、32ビット整数の変数、4バイトの時刻欄を持つファイル形式、更新されていない組み込み機器など)です。

Q. NTP の時刻や Windows の FILETIME と Unix 時刻は違いますか?
起点と単位が違います。NTP は1900年1月1日が起点で、Unix 時刻との差は 2208988800 秒(1900年から1970年までの 25567 日分)です。Windows の FILETIME は1601年1月1日からの100ナノ秒単位で、差は 116444736000000000 です。

Q. うるう秒は Unix タイムスタンプに含まれますか?
含まれません。1日を86400秒として数えるため、23:59:60 を表す値がありません。手元の date コマンドでも invalid date になりました。

Q. ISO 8601 と RFC 3339 の違いは?
RFC 3339 は、ISO 8601 の書き方のうちインターネットで使う形を絞り込んだものです。2026-01-01T00:00:00Z や 2026-01-01T09:00:00+09:00 はどちらにも当てはまります。

Q. 記事内のツールに入れた値はどこかに送られますか?
送られません。変換はブラウザの中だけで計算しています。

まとめ

  • Unix タイムスタンプは 1970-01-01 00:00:00 UTC からの経過秒数で、時刻帯を持たない。日本時間は UTC の日時に9時間を足した表示。
  • 10桁は秒、13桁はミリ秒。JavaScript と Java の標準はミリ秒。取り違えると1970年1月や西暦57971年になり、MySQL・SQLite は NULL を返す。
  • 9時間ずれは、時刻帯を書かない日時をサーバーの TZ 任せで変換したときに起きる。時刻帯は引数か +09:00・Z で明示する。
  • 2038年問題は 2147483647 の1秒後に32ビットが溢れる問題。MySQL の TIMESTAMP 型・int の変数・ビット演算の切り捨てを確かめる。
  • うるう秒は Unix 時刻に入らない。正確な経過秒数が要る計測には向かない。

参考・出典

確認日はいずれも 2026年11月7日です。

  • The Open Group「The Open Group Base Specifications Issue 8」4.19 Seconds Since the Epoch:https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap04.html
  • MySQL「MySQL 8.0 Reference Manual」14.7 Date and Time Functions(FROM_UNIXTIME・UNIX_TIMESTAMP):https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html
  • MySQL「MySQL 8.0 Reference Manual」13.2.2 The DATE, DATETIME, and TIMESTAMP Types:https://dev.mysql.com/doc/refman/8.0/en/datetime.html
  • PostgreSQL「PostgreSQL 16 Documentation」9.9 Date/Time Functions and Operators:https://www.postgresql.org/docs/16/functions-datetime.html
  • Python「datetime — Basic date and time types」:https://docs.python.org/3/library/datetime.html
  • MDN「Date」:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date
  • Go「Package time」:https://pkg.go.dev/time
  • Oracle「Instant (Java SE 21)」:https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/Instant.html
  • PHP「date」:https://www.php.net/manual/ja/function.date.php
  • SQLite「Date And Time Functions」:https://www.sqlite.org/lang_datefunc.html
  • IETF「RFC 3339 Date and Time on the Internet: Timestamps」:https://www.rfc-editor.org/rfc/rfc3339
  • IETF「RFC 7519 JSON Web Token (JWT)」2. Terminology(NumericDate):https://www.rfc-editor.org/rfc/rfc7519
  • BIPM「Resolution 4 of the 27th CGPM (2022) On the use and future development of UTC」:https://www.bipm.org/en/cgpm-2022/resolution-4

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Unixタイムスタンプ変換|エポック秒⇔日時(JST・UTC)とミリ秒・2038年」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

成長のヒミツ、 ぜんぶ丸わかり