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

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

正規URL: https://3mikan.com/archives/6081

著者: みかん
公開: 2026-09-06T00:00:00.000Z
更新: 2026-09-06T00:00:00.000Z

ブロックの時刻を画面に出したら、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)`が扱う単位は**ミリ秒**です。[^rpc] [^solidity] [^date]

| 値 | 単位・役割 | 取り違えやすい点 |
| --- | --- | --- |
| ブロックの`timestamp` | 秒 | そのまま`Date`へ渡さない |
| `Date.now()` | ミリ秒 | ブロック時刻ではなく、実行環境の現在時刻 |
| `Date`の`getTime()` | ミリ秒 | 秒へ戻すなら端数の扱いを決める |
| 日本時間で整形した文字列 | 表示 | 時刻の比較や保存の正本にしない |

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

## 1970年になる例を、接続せずに再現する

次の`1700000000`を、秒として解釈したい場面を考えます。

```js
const seconds = 1_700_000_000n;

console.log(new Date(Number(seconds)).toISOString());
console.log(new Date(Number(seconds * 1000n)).toISOString());
```

実行結果は次のとおりです。

```text
1970-01-20T16:13:20.000Z
2023-11-14T22:13:20.000Z
```

最初の行は、17億「秒」を17億「ミリ秒」として読んでいます。2行目は、1秒が1000ミリ秒であることを反映した変換です。この例では、タイムゾーンを変えても最初の単位の間違いは直りません。

`0x6553f100`という形式で取得した数量を、検証後に`BigInt('0x6553f100')`へ変換しても値は`1700000000n`です。**16進数から整数に直すことと、秒からミリ秒に直すことは別の工程**として扱います。RPCの形式は取得元のメソッド定義で確認します。[^rpc] 16進表記そのものの検査は、[QUANTITYとDATAの区別](/archives/6071)で説明しています。

## 範囲を確かめてからDateへ渡す

前の例は固定した小さな値でした。外部から受け取る値に使うなら、型と範囲を検査する関数へ分けます。

```js
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時刻が一般に不正という意味ではありません。[^date]

`Number`への変換は、乗算と範囲検査の後に一度だけ行います。検査後の整数ミリ秒は`Number`の安全な整数範囲内に収まるため、この変換では整数の桁を失いません。任意の`uint256`を先に`Number`へ変えてよい、という一般化はできません。[^date]

確認すべき結果は次のようになります。

| 入力 | 結果 |
| --- | --- |
| `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で表示が違う原因になります。[^intl]

```js
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));
```

今回確認した環境では、次の表示になりました。

```text
2023-11-14T22:13:20.000Z
2023/11/15 7:13:20
```

同じ`instant`を使っています。日付が翌日に変わっても、元の時刻を足し引きしていません。表示の区切りや細部は国際化データに依存するため、機械が読み戻す保存形式には`getTime()`の値やUTCのISO文字列を使い、人向け表示と分けます。[^intl]

日時文字列を入力として受け取る場合も、オフセットを明示します。次の2つが同じ値になることを確認できます。

```js
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()`から作った画面上の残り時間だけで、将来のコントラクト実行を保証しないようにします。画面の時計と、処理が評価するブロック時刻は別の入力です。[^solidity]

手元のミリ秒を非負の整数秒へ変える場合は、たとえば次のように端数を落とします。

```js
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時間を足さず、表示側のタイムゾーンを指定します。期限では両辺の単位と等号の扱いを揃えます。

保存する値に単位を明記し、未取得をゼロへ変えず、変換を一か所へまとめる。この設計なら、見た目だけを直して別の時刻を保存してしまう経路を減らせます。ブロックを選んだ条件や取引との対応を調べる場合は、[取引情報と実行結果を分けて確認する手順](/archives/6001)も参照してください。

## 実行環境と出典

コードはNode.js v22.16.0、ICU 77.1で2026-09-06に確認しました。UTC、日本時間、米国東部時間を既定値とする3環境でも、明示したUTC・日本時間の出力が同じになることを確認しています。ネットワーク上の実時刻や取引は取得していません。

[^rpc]: [Ethereum JSON-RPC API：ブロックのtimestampと数量表現](https://ethereum.org/developers/docs/apis/json-rpc/)。確認日：2026-09-06。
[^solidity]: [Solidity：Block and Transaction Properties](https://docs.soliditylang.org/en/latest/units-and-global-variables.html#block-and-transaction-properties)。`block.timestamp`の単位を確認。確認日：2026-09-06。参照ページの開発版表示をコンパイラーの実行履歴とは扱っていません。
[^date]: [ECMAScript 2025：Date Objects、TimeClip、Numberの安全な整数範囲](https://tc39.es/ecma262/2025/multipage/numbers-and-dates.html#sec-date-objects)。確認日：2026-09-06。
[^intl]: [ECMAScript Internationalization API：Intl.DateTimeFormat](https://tc39.es/ecma402/2025/#sec-intl-datetimeformat-constructor)。確認日：2026-09-06。

## 確認した一次情報

- [Ethereum JSON-RPC: block timestamp](<https://ethereum.org/developers/docs/apis/json-rpc/>): 確認日 2026-09-06
- [Solidity: block.timestamp in seconds](<https://docs.soliditylang.org/en/latest/units-and-global-variables.html#block-and-transaction-properties>): 確認日 2026-09-06
- [ECMAScript 2025: Date and TimeClip](<https://tc39.es/ecma262/2025/multipage/numbers-and-dates.html#sec-date-objects>): 確認日 2026-09-06
- [ECMA-402: explicit timeZone](<https://tc39.es/ecma402/2025/#sec-intl-datetimeformat-constructor>): 確認日 2026-09-06
