2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

toLocaleString を2万回呼ぶと遅い? Node.js と WebKit で Intl.DateTimeFormat 使い回しと実測

2
Posted at

一覧画面で日付を「2026/01/01 09:00」の形に整えるとき、各行で toLocaleString("ja-JP", { ... }) を呼ぶ書き方があります。AI に日付の表示を頼んでも、この形で返ってくることがあります。

この書き方を2万件で測ると、Node.js では Intl.DateTimeFormat を1つ作って使い回す場合の約40倍、macOS の WebKit(Safari と同じエンジン)では約60倍の時間がかかりました。さらに、どの引数から遅くなるかは Node.js と WebKit で違いました。この記事では、その測定結果を整理します。

測定条件

  • Node.js v24.14.1(Apple M4・タイムゾーン Asia/Tokyo)。日付2万件を1回ずつ整形し、7回計測した中央値(最初に1回空回し)。同じ計測を2回実行して、同じ傾向になることを確認
  • WebKit は macOS の WebKit(Safari と同じエンジン・cmux というアプリに内蔵のブラウザで計測)で、5回計測した中央値
  • Chrome と Firefox は測っていません
  • 数値は環境や実行のたびに変わります。倍率の目安として見てください
const dates = Array.from({ length: 20000 }, (_, i) =>
  new Date(Date.UTC(2026, 0, 1) + i * 3_600_000)
);
const opts = {
  year: "numeric", month: "2-digit", day: "2-digit",
  hour: "2-digit", minute: "2-digit", timeZone: "Asia/Tokyo",
};

// A: 毎回 toLocaleString にロケールとオプションを渡す
for (const d of dates) d.toLocaleString("ja-JP", opts);

// B: Intl.DateTimeFormat を1つ作って使い回す
const fmt = new Intl.DateTimeFormat("ja-JP", opts);
for (const d of dates) fmt.format(d);

どちらも同じ文字列(先頭の日付なら 2026/01/01 09:00)になることを確認しています。

時間の測り方(クリックで開く)
import { performance } from "node:perf_hooks";

function median(a) {
  const s = [...a].sort((x, y) => x - y);
  return s[Math.floor(s.length / 2)];
}
function bench(name, fn, runs = 7) {
  fn(); // 1回空回し
  const t = [];
  for (let r = 0; r < runs; r++) {
    const start = performance.now();
    fn();
    t.push(performance.now() - start);
  }
  console.log(name, median(t).toFixed(2), "ms");
}

bench("toLocaleString(ja-JP, opts)", () => {
  for (const d of dates) d.toLocaleString("ja-JP", opts);
});

結果(Node.js・2万件)

書き方 時間(中央値)
d.toLocaleString()(引数なし) 約11 ms
d.toLocaleString("ja-JP")(ロケールだけ) 約10 ms
d.toLocaleString(undefined, opts)(オプションだけ) 約390 ms
d.toLocaleString("ja-JP", opts) 約420〜440 ms
new Intl.DateTimeFormat("ja-JP", opts).format(d) を毎回 約430 ms
Intl.DateTimeFormat を1つ作って使い回す 約11 ms

オプションを渡した toLocaleString は、Intl.DateTimeFormat を毎回 new するのとほぼ同じ時間でした。使い回した場合の format は1回あたり約0.5マイクロ秒なので、差のほとんどは Intl.DateTimeFormat を作る処理にかかっています。

Node.js(V8)では、オプションを渡さない呼び出しだけが速いままでした。実測の結果からは、オプションなしのときだけ内部で準備済みのものが使い回されているように見えます(内部実装までは確認していないので、推測です)。

toLocaleDateString("ja-JP", { month: "long" }) のように、オプションを1つ渡しただけでも約410 ms で、同じく遅い側でした。

ただし、引数なしやロケールだけの呼び出しは、出力の形式が違います。手元では toLocaleString() が 1/1/2026, 9:00:00 AM、toLocaleString("ja-JP") が 2026/1/1 9:00:00 でした。引数なしは実行した環境の言語設定でも形式が変わるので、「速いから」といって 2026/01/01 09:00 の代わりには使えません。

結果(WebKit・2万件)

書き方 時間(中央値)
d.toLocaleString()(引数なし) 約11 ms
d.toLocaleString("ja-JP")(ロケールだけ) 約650 ms
d.toLocaleString("ja-JP", opts) 約680 ms
Intl.DateTimeFormat を1つ作って使い回す 約11 ms

WebKit では、ロケールを渡しただけでも遅い側になりました。Node.js で速かった「ロケールだけ」の書き方が、WebKit(Safari)でも速いとは限らないということです。

今回測った2つの環境で、表示の形式を指定したうえで速かったのは「Intl.DateTimeFormat を1つ作って使い回す」書き方だけでした(引数なしも速いですが、形式は指定できません)。

MDN の Date.prototype.toLocaleString のページにも、同じ引数で何度も呼ぶ場合は Intl.DateTimeFormat を作って format() を使うほうがよい、という趣旨の説明があります。

数値の toLocaleString も同じ傾向(Node.js のみ)

金額をカンマ区切りや「¥」付きで表示するときの Number.prototype.toLocaleString でも、Node.js で同じ傾向が出ました(2万件・WebKit では未計測)。

書き方 時間(中央値)
n.toLocaleString("ja-JP") 約4 ms
n.toLocaleString("ja-JP", { style: "currency", currency: "JPY" }) 約280 ms
new Intl.NumberFormat("ja-JP", { style: "currency", currency: "JPY" }) を使い回す 約5 ms

100件なら気にしなくていい(Node.js での値)

同じ比較を Node.js で100件にすると、オプション付きの toLocaleString が約2 ms、使い回しが約0.06 ms でした。倍率は大きくても、絶対値では画面の表示にほとんど影響しない差です。WebKit は1回あたりが Node.js より重かったので(約34マイクロ秒)、件数が多い画面ほど差が出やすくなります。

気にしたほうがいいのは、次のような場面です。

  • 数千〜数万行の一覧や表を一度に描画する
  • CSV の書き出しやログの整形など、ループの中で大量に日付を整形する
  • React の部品の中で整形していて、再描画のたびに全行ぶん呼ばれる

使い回すときの書き方

部品の外(モジュールの一番上)で1回だけ作り、各行では format だけを呼びます。

// dateFormat.js
export const dateTimeFormat = new Intl.DateTimeFormat("ja-JP", {
  year: "numeric", month: "2-digit", day: "2-digit",
  hour: "2-digit", minute: "2-digit", timeZone: "Asia/Tokyo",
});
// Row.jsx
import { dateTimeFormat } from "./dateFormat.js";

export function Row({ row }) {
  return <td>{dateTimeFormat.format(new Date(row.createdAt))}</td>;
}

timeZone を指定しておくと、サーバーとブラウザのタイムゾーンの違いで表示がずれることはなくなります。指定しないと、実行した環境のタイムゾーンで整形されます。

AI が書いた日付表示を見るときの確認ポイント

  • ループや map の中で、ロケールやオプションを渡した toLocaleString / toLocaleDateString を呼んでいないか(WebKit ではロケールだけでも遅かった)
  • 同じオプションの new Intl.DateTimeFormat(...) を、行ごとに作っていないか
  • 件数が少ない画面なら、そのままで問題ない(読みやすさを優先してよい)

まとめ

  • オプションを渡した toLocaleString は、毎回 Intl.DateTimeFormat を作るのとほぼ同じ時間がかかった(Node.js で使い回しの約40倍、WebKit で約60倍)
  • どの引数から遅くなるかは環境で違った(ロケールだけなら Node.js では速く、WebKit では遅かった)
  • 形式を指定して大量に整形するなら、Intl.DateTimeFormat を1つ作って使い回すのが、今回測った Node.js と WebKit の両方で速かった(Intl.NumberFormat も Node.js で同じ傾向を確認)
  • 100件程度なら、Node.js での差は約2 ms で、気にしなくてよい

未経験者向けの講座を運営しています

未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を、全24セッションで体系化した教材です。Claude Code を学習パートナーにする CLAUDE.md と学習モード(learner / developer)の設計までセットで含みます。

※ 中上級者には易しすぎる内容なので、初心者の知り合いへの紹介や社内研修の参考としてどうぞ。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?