ログの時刻、APIのレスポンス、DBに入っていた数値——1700000000 のような数字を見て「これ、結局いつ?」となること、ありませんか。逆に「今のUNIX時間がサッと欲しい」場面も地味に多いです。
UNIXタイムスタンプ(エポック秒)と日時の変換は、ブラウザ標準の Date だけで完結します。ライブラリも通信も不要です。この記事では、秒⇔日時の相互変換と、ハマりやすい「秒とミリ秒」「タイムゾーン」の罠をまとめます。
ゴール:
1700000000のような数値を、ライブラリなしで日時に(そしてその逆に)正しく変換できるようになること。
そもそもUNIXタイムスタンプとは
1970年1月1日 00:00:00 UTC からの経過秒数のことです(エポック秒とも呼びます)。タイムゾーンに依存しない「世界共通の一点」を1つの数値で表せるので、ログやDB、API間のやり取りでよく使われます。
ここで最初の落とし穴。**UNIXタイムスタンプは「秒」だけど、JavaScriptの Date は「ミリ秒」**で動きます。この単位の食い違いが、変換ミスの9割の原因です(後述)。
現在のUNIXタイムスタンプを取得する
Date.now() は現在時刻をミリ秒で返します。UNIXタイムスタンプ(秒)が欲しいなら、1000で割って切り捨てます。
const nowMs = Date.now(); // 例: 1700000000123(ミリ秒)
const nowSec = Math.floor(Date.now() / 1000); // 例: 1700000000(秒)
Math.round ではなく Math.floor(切り捨て) を使うのがポイント。四捨五入にすると、ミリ秒部分が500以上のときに1秒進んでしまい、実際の時刻より未来になってしまいます。
UNIXタイムスタンプ → 日時
new Date() にミリ秒を渡すと Date オブジェクトになります。手元にあるのが秒なら、1000倍してから渡します。
function unixToDate(sec) {
return new Date(sec * 1000); // 秒 → ミリ秒にしてから渡す
}
const d = unixToDate(1700000000);
d.toLocaleString(); // 端末のローカル時刻で表示(例: 2023/11/15 6:13:20)
d.toISOString(); // UTCのISO8601(例: 2023-11-14T22:13:20.000Z)
ここを new Date(1700000000) と1000倍し忘れると、1970年1月20日あたりの意味不明な日時になります。「やけに昔の日付が出る」ときは、まず秒とミリ秒を疑ってください。
日時 → UNIXタイムスタンプ
逆方向も同じ発想です。Date から getTime()(ミリ秒)を取り、1000で割って切り捨てます。
function dateToUnix(date) {
return Math.floor(date.getTime() / 1000);
}
dateToUnix(new Date("2023-11-15T00:00:00+09:00")); // 1699974000(秒)
date.getTime() と Date.now() はどちらもミリ秒。粒度を合わせたいとき以外は、最後に /1000 して Math.floor で統一しておくと混乱しません。
落とし穴①:秒かミリ秒かを「桁数」で見分ける
もらった数値が秒なのかミリ秒なのか分からない——これはよくあります。目安として、現在近辺の秒は10桁、ミリ秒は13桁です。
function normalizeToMs(num) {
// 13桁 ≒ ミリ秒、10桁 ≒ 秒 とみなす素朴な判定
return String(num).length >= 13 ? num : num * 1000;
}
new Date(normalizeToMs(1700000000)); // 秒とみなして ×1000
new Date(normalizeToMs(1700000000123)); // ミリ秒とみなしてそのまま
厳密な判定ではありませんが、2001年〜2286年くらいの範囲なら桁数で十分に見分けられます。外部から来る値を扱うときの保険として便利です。
落とし穴②:タイムゾーン(ローカルとUTCの取り違え)
Date の表示系メソッドは、どれが「ローカル」でどれが「UTC」かを意識しないと事故ります。
-
toLocaleString()… 端末のタイムゾーンで表示(人に見せる用) -
toISOString()… 常にUTC。末尾にZ(=UTC)が付く -
getHours()などはローカル、getUTCHours()などはUTC
const d = new Date(1700000000 * 1000);
d.toISOString(); // "2023-11-14T22:13:20.000Z"(UTC)
d.toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" }); // 日本時間で表示
「ISOで出したら9時間ずれてる!」の正体は、たいてい toISOString() がUTCだからです。ずれているのではなく、UTCで正しく出ているだけ。日本時間で見せたいなら toLocaleString に timeZone を渡します。
落とし穴③:日付文字列のパースはブラウザ任せにしない
new Date("2023-11-15") のような文字列パースは、形式によって解釈がUTC扱いになったりローカル扱いになったりします("2023-11-15" はUTC午前0時、"2023/11/15" はローカル午前0時、など)。
安全なのは、数値の各要素から組み立てる方法です。
// ローカル時刻として組み立て(月は0始まりに注意)
const local = new Date(2023, 10, 15, 0, 0, 0); // 2023-11-15 00:00 ローカル
// UTCとして組み立てたいとき
const utcMs = Date.UTC(2023, 10, 15, 0, 0, 0); // ミリ秒が返る
const utc = new Date(utcMs);
Date の月は0始まり(1月=0、11月=10)です。ここも定番のバグ源なので要注意。
落とし穴④:2038年問題は「どこで」起きるか
UNIXタイムスタンプを32ビット符号付き整数で持つシステムは、2038年1月19日にオーバーフローします(いわゆる2038年問題)。
ただし JavaScript 側は心配無用です。Date は64ビット浮動小数点でミリ秒を扱うので、約27万年先まで表現できます。問題が起きるのは、受け渡し先のC/組み込み/古いDBなど32ビットで秒を保持している側です。フロントで変換するだけなら気にしなくて大丈夫ですが、「送った先がどう保持しているか」は頭の片隅に置いておくと安心です。
まとめ
- UNIXタイムスタンプ=1970年からの経過"秒"。
Dateは**"ミリ秒"**で動く(ここの単位差が最大の罠) - 現在の秒:
Math.floor(Date.now() / 1000)(四捨五入はダメ) - 秒 → 日時:
new Date(sec * 1000)(1000倍を忘れると1970年になる) - 日時 → 秒:
Math.floor(date.getTime() / 1000) - 秒かミリ秒かは**桁数(10 or 13)**で見分けると実務的
-
toISOString()は常にUTC、toLocaleString()はローカル。ずれて見えるのはたいてい正常 - 文字列パースは形式依存で危険。各要素から組み立て、月は0始まり
- 2038年問題はJS側では起きない。保持する先の話
数値と日時を行き来する処理は、Date 標準メソッドだけで十分まかなえます。参考になれば嬉しいです。