Ethereumの時刻が1970年になるのはなぜ?timestampの秒・ミリ秒・日本時間を整理する
EthereumのtimestampをJavaScriptのDateへ渡すと1970年になる原因を、小さな実行例で解説します。秒とミリ秒の変換、BigIntからの範囲検査、日本時間の表示、期限ちょうどの判定を分けて確認します。

ブロックの時刻を画面に出したら、2023年のはずが1970年になった。日本時間へ直そうと9時間を足したら、別の画面ではさらに9時間ずれた。こうした不具合では、時刻そのものよりも、値の単位と表示方法を混ぜていないかを先に確認します。
この記事は、Ethereumのブロック時刻や期限をJavaScriptで表示する開発者向けです。秒をミリ秒へ変える場所、BigIntからNumberへ変換してよい条件、タイムゾーンの指定、期限ちょうどの判定を分けて整理します。
以下の日時と数値は説明用に固定した値です。実在する取引の実行時刻を測ったものではなく、コードの実行にウォレット、アカウント、外部RPCは必要ありません。
掲載例はNode.js 22以降を使い、この記事のJavaScriptを上から順に同じexample.mjsへ保存して、node example.mjsで実行できます。テキスト形式で示す出力や表は、ファイルへ含めません。
同じ「時刻」でも数える単位が違う
Ethereum JSON-RPCのブロックにはtimestampがあります。Solidityのblock.timestampも、Unix epochからの秒数を表します。一方、数値を一つ渡すnew Date(value)が扱う単位はミリ秒です。1 2 3
| 値 | 単位・役割 | 取り違えやすい点 |
|---|---|---|
ブロックのtimestamp |
秒 | そのままDateへ渡さない |
Date.now() |
ミリ秒 | ブロック時刻ではなく、実行環境の現在時刻 |
DateのgetTime() |
ミリ秒 | 秒へ戻すなら端数の扱いを決める |
| 日本時間で整形した文字列 | 表示 | 時刻の比較や保存の正本にしない |
まず、変数名にも単位を入れます。timeだけより、blockTimestampSecondsやreceivedAtMsのほうが、値を受け渡すときに確認しやすくなります。
1970年になる例を、接続せずに再現する
次の1700000000を、秒として解釈したい場面を考えます。
const seconds = 1_700_000_000n;
console.log(new Date(Number(seconds)).toISOString());
console.log(new Date(Number(seconds * 1000n)).toISOString());
実行結果は次のとおりです。
1970-01-20T16:13:20.000Z
2023-11-14T22:13:20.000Z
最初の行は、17億「秒」を17億「ミリ秒」として読んでいます。2行目は、1秒が1000ミリ秒であることを反映した変換です。この例では、タイムゾーンを変えても最初の単位の間違いは直りません。
0x6553f100という形式で取得した数量を、検証後にBigInt('0x6553f100')へ変換しても値は1700000000nです。16進数から整数に直すことと、秒からミリ秒に直すことは別の工程として扱います。RPCの形式は取得元のメソッド定義で確認します。1 16進表記そのものの検査は、QUANTITYとDATAの区別で説明しています。
範囲を確かめてからDateへ渡す
前の例は固定した小さな値でした。外部から受け取る値に使うなら、型と範囲を検査する関数へ分けます。
function secondsToDate(seconds) {
if (typeof seconds !== 'bigint') {
throw new TypeError('seconds must be a bigint');
}
if (seconds < 0n) {
throw new RangeError('seconds must be non-negative');
}
const milliseconds = seconds * 1000n;
if (milliseconds > 8_640_000_000_000_000n) {
throw new RangeError('timestamp exceeds the Date range');
}
return new Date(Number(milliseconds));
}
ECMAScriptのDateが扱う範囲は、epochの前後それぞれ8.64×10の15乗ミリ秒までです。この関数はブロック時刻を読む目的で、非負の整数秒だけを受け付けます。負のUnix時刻が一般に不正という意味ではありません。3
Numberへの変換は、乗算と範囲検査の後に一度だけ行います。検査後の整数ミリ秒はNumberの安全な整数範囲内に収まるため、この変換では整数の桁を失いません。任意のuint256を先にNumberへ変えてよい、という一般化はできません。3
確認すべき結果は次のようになります。
| 入力 | 結果 |
|---|---|
0n |
1970-01-01T00:00:00.000Z |
1700000000n |
2023-11-14T22:13:20.000Z |
-1n |
この関数の入力方針によりRangeError |
'1700000000' |
文字列なのでTypeError |
null |
未取得をゼロへ補完せずTypeError |
8640000000001n |
Dateの範囲を超えるためRangeError |
ここで重要なのは、範囲検査だけでは単位の正しさを証明できないことです。たとえば、すでにミリ秒になっている1700000000000nを秒として渡しても、非常に遠い未来の有効な日付になる場合があります。桁数で自動判定せず、APIとデータ形式の取り決めで単位を固定します。
日本時間は9時間を足すのではなく、表示へ指定する
UTCの日時と日本時間の日時は、同じ瞬間を別の表示で表したものです。保存したミリ秒へ9時間分を足すと、表示設定を変えるのではなく、別の瞬間へ移動してしまいます。
次のように、Intl.DateTimeFormatへtimeZoneを指定します。省略した場合は実行環境のタイムゾーンが使われるため、サーバーと手元のPCで表示が違う原因になります。4
const instant = secondsToDate(1_700_000_000n);
const tokyoFormatter = new Intl.DateTimeFormat('ja-JP', {
timeZone: 'Asia/Tokyo',
dateStyle: 'medium',
timeStyle: 'medium',
hourCycle: 'h23',
});
console.log(instant.toISOString());
console.log(tokyoFormatter.format(instant));
今回確認した環境では、次の表示になりました。
2023-11-14T22:13:20.000Z
2023/11/15 7:13:20
同じinstantを使っています。日付が翌日に変わっても、元の時刻を足し引きしていません。表示の区切りや細部は国際化データに依存するため、機械が読み戻す保存形式にはgetTime()の値やUTCのISO文字列を使い、人向け表示と分けます。4
日時文字列を入力として受け取る場合も、オフセットを明示します。次の2つが同じ値になることを確認できます。
console.log(
Date.parse('2023-11-14T22:13:20Z') ===
Date.parse('2023-11-15T07:13:20+09:00')
);
// true
期限判定は表示文字列ではなく、同じ単位で比較する
「締切の時刻になったら無効」と「締切の時刻までは有効」は、等号の扱いが違います。文章だけで決めず、実装が<と<=のどちらを使っているかを確認します。
| 比較する秒 | blockTime < deadline |
blockTime <= deadline |
|---|---|---|
| 期限の1秒前 | 有効 | 有効 |
| 期限と同じ | 無効 | 有効 |
| 期限の1秒後 | 無効 | 無効 |
これは比較演算の違いを示す表であり、特定のサービスの有効期限を保証するものではありません。Date.now()から作った画面上の残り時間だけで、将来のコントラクト実行を保証しないようにします。画面の時計と、処理が評価するブロック時刻は別の入力です。2
手元のミリ秒を非負の整数秒へ変える場合は、たとえば次のように端数を落とします。
function epochMsToSeconds(milliseconds) {
if (typeof milliseconds !== 'number' ||
!Number.isSafeInteger(milliseconds) ||
milliseconds < 0 || milliseconds > 8_640_000_000_000_000) {
throw new RangeError('milliseconds must be a non-negative Date-range integer');
}
return BigInt(milliseconds) / 1000n;
}
console.log(epochMsToSeconds(1_700_000_000_123).toString());
// 1700000000
この関数は、末尾の123ミリ秒を切り捨てます。四捨五入にすると、999ミリ秒の段階で次の秒へ進んでしまうため、同じ意味の変換にはなりません。
まとめ:単位・表示・判定を別々に直す
1970年が出たら、まず秒をミリ秒として読んでいないか確認します。日本時間への変換では保存値へ9時間を足さず、表示側のタイムゾーンを指定します。期限では両辺の単位と等号の扱いを揃えます。
保存する値に単位を明記し、未取得をゼロへ変えず、変換を一か所へまとめる。この設計なら、見た目だけを直して別の時刻を保存してしまう経路を減らせます。ブロックを選んだ条件や取引との対応を調べる場合は、取引情報と実行結果を分けて確認する手順も参照してください。
実行環境と出典
コードはNode.js v22.16.0、ICU 77.1で2026-09-06に確認しました。UTC、日本時間、米国東部時間を既定値とする3環境でも、明示したUTC・日本時間の出力が同じになることを確認しています。ネットワーク上の実時刻や取引は取得していません。
Footnotes
-
Ethereum JSON-RPC API:ブロックのtimestampと数量表現。確認日:2026-09-06。 ↩ ↩2
-
Solidity:Block and Transaction Properties。
block.timestampの単位を確認。確認日:2026-09-06。参照ページの開発版表示をコンパイラーの実行履歴とは扱っていません。 ↩ ↩2 -
ECMAScript 2025:Date Objects、TimeClip、Numberの安全な整数範囲。確認日:2026-09-06。 ↩ ↩2 ↩3
-
ECMAScript Internationalization API:Intl.DateTimeFormat。確認日:2026-09-06。 ↩ ↩2
確認した一次情報
- Ethereum JSON-RPC: block timestamp確認日: 2026/09/06
- Solidity: block.timestamp in seconds確認日: 2026/09/06
- ECMAScript 2025: Date and TimeClip確認日: 2026/09/06
- ECMA-402: explicit timeZone確認日: 2026/09/06



