3MIKAN
仮想通貨直コン

Ethereumの時刻が1970年になるのはなぜ?timestampの秒・ミリ秒・日本時間を整理する

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

3MIKANのキャラクターが同じ長さのリボンに目盛りの細かさが違う物差しを当て、単位の違いを確かめる場面

ブロックの時刻を画面に出したら、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() ミリ秒 ブロック時刻ではなく、実行環境の現在時刻
DategetTime() ミリ秒 秒へ戻すなら端数の扱いを決める
日本時間で整形した文字列 表示 時刻の比較や保存の正本にしない

まず、変数名にも単位を入れます。timeだけより、blockTimestampSecondsreceivedAtMsのほうが、値を受け渡すときに確認しやすくなります。

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.DateTimeFormattimeZoneを指定します。省略した場合は実行環境のタイムゾーンが使われるため、サーバーと手元の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

  1. Ethereum JSON-RPC API:ブロックのtimestampと数量表現。確認日:2026-09-06。 2

  2. Solidity:Block and Transaction Propertiesblock.timestampの単位を確認。確認日:2026-09-06。参照ページの開発版表示をコンパイラーの実行履歴とは扱っていません。 2

  3. ECMAScript 2025:Date Objects、TimeClip、Numberの安全な整数範囲。確認日:2026-09-06。 2 3

  4. ECMAScript Internationalization API:Intl.DateTimeFormat。確認日:2026-09-06。 2

確認した一次情報