- 技術 💻
Unixタイムスタンプ変換|エポック秒⇔日時(JST・UTC)とミリ秒・2038年
結論
Unix タイムスタンプ(エポック秒)は「1970年1月1日 0時0分0秒(UTC)から何秒たったか」を表す数です。日本時間(JST)にするには UTC の日時に9時間を足します。10桁なら秒、13桁ならミリ秒です。
この記事でわかること
- エポック秒⇔日時(JST・UTC)をその場で変換するツールの使い方
- 7つの言語・コマンドと MySQL・PostgreSQL で変換した実行結果
- 秒とミリ秒・時刻帯の取り違え、2038年問題を実際に再現した原文
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 タイムスタンプ変換ツールの使い方(桁数の判定と時刻帯)
この節では、記事内のツールが数字をどう読むのか、結果の表のどこを見ればよいのかが分かります。
数字を貼る
ログや API の応答にある数字を「Unix 時刻 → 日時」の欄に貼ります。入れたとたんに結果が出ます。
単位の判定を見る
入力欄の下に「桁数(13桁)から『ミリ秒』として読みました」のように、どの単位として読んだかが出ます。思っていた単位と違えば、元のデータの単位を疑います。
日本時間と UTC を両方見る
表の1行目が日本時間、2行目が UTC です。ログの時刻と9時間ずれていたら、ログ側がどちらで書かれているかが分かります。
逆向きは時刻帯を選ぶ
「日時 → 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桁です。 手元で同じ瞬間に近いタイミングで取った値を並べると、桁数の違いがはっきり見えます。
| 言語・道具 | いまの時刻の取り方 | 単位 | 手元の出力 |
|---|---|---|---|
| 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 に渡すとき(またはその逆)に、取り違えが起きます。見分け方は単純です。
- 110桁 → 秒2001年9月〜2286年11月の日時
- 213桁 → ミリ秒1000で割ると秒。JavaScript・Java から来た値
- 316桁 → マイクロ秒データベースや分散トレーシングの記録
- 419桁 → ナノ秒Go の UnixNano、Python の time_ns
- 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:00ZISO 8601(RFC 3339)の形で時刻帯まで書けば、どの言語・どのサーバーでも同じ瞬間になる
エポック秒を日時にする
手元とコンテナで 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期連続の黒字決算。