Intl APIを使った日付と数値のフォーマット
Intl.DateTimeFormatとIntl.NumberFormatを使ったことがあるでしょう。しかし、あなたのメンタルモデルが数年前のものであれば、重要な機能を見逃している可能性があります。特にIntl.NumberFormatの丸め制御機能や、2025年後半のランタイムにおけるTemporalとIntlの連携についてです。
この記事では、Intl APIを通じたJavaScriptの国際化について、最新の技術的概要を提供します。何が変わったのか、そして経験豊富な開発者がよく誤解している点に焦点を当てます。
重要なポイント
- Intl APIは値をロケール対応の文字列にフォーマットしますが、データの解析、計算、保存は行いません。プレゼンテーション層とアプリケーションロジックは分離しておきましょう。
Intl.NumberFormatは、roundingMode、roundingIncrement、trailingZeroDisplayなどの高度な丸め制御をサポートするようになりました。- Temporal型は独自の
toLocaleString()フォーマットを処理し、Intl.DateTimeFormatは引き続きDateオブジェクトをフォーマットします。 - パフォーマンスのためにフォーマッターインスタンスを再利用し、テストで期待される出力文字列をハードコーディングしないようにしましょう。
Intlが実際に行うこと
Intl名前空間は、値のロケール対応フォーマットを処理します。データの解析、計算、操作は行いません。文化的慣習に従って、値を人間が読める文字列に変換します。
2つの重要なポイント:
- Intlはフォーマットを行い、保存や計算は行いません。 アプリケーションロジックはプレゼンテーション層から分離されたままです。
- 出力はランタイムによって異なります。 基礎となるロケールデータは、ブラウザやNode.jsにバンドルされているICUライブラリから提供されます。正確な文字列は、ChromeとSafari、またはNodeのバージョン間でわずかに異なる場合があります。
テストで期待される出力文字列をハードコーディングしないでください。代わりにformatToParts()または構造的なアサーションを使用してください。
Intl.DateTimeFormat: 基本オプションを超えて
Dateオブジェクトのフォーマット
Intl.DateTimeFormatは、JavaScript Dateオブジェクトをフォーマットする標準的な方法です:
const formatter = new Intl.DateTimeFormat('de-DE', {
dateStyle: 'long',
timeStyle: 'short',
timeZone: 'Europe/Berlin'
});
formatter.format(new Date()); // "27. Juni 2025 um 14:30"
dateStyleとtimeStyleオプションは、プリセット設定を提供します。きめ細かい制御が必要な場合は、weekday、month、hour、timeZoneNameなどの個別オプションを使用してください。
Temporal型とロケール対応フォーマット
Temporalは現代のJavaScriptエンジンで積極的に実装されていますが、すべてのブラウザと環境で広くサポートされているわけではありません。利用可能な場合、Temporal.PlainDate、Temporal.ZonedDateTime、その他のTemporal型は、Intl.DateTimeFormat.prototype.format()に直接渡されるのではなく、toLocaleString()を介して自身をフォーマットします。
const date = Temporal.PlainDate.from('2025-06-27');
date.toLocaleString('ja-JP', { dateStyle: 'full' });
// "2025年6月27日金曜日"
Intl.DateTimeFormatはDateオブジェクトを受け入れます。Temporal型は独自のフォーマットロジックを処理し、内部的にロケール依存の動作を委譲する場合がありますが、Intl.DateTimeFormat自体によってフォーマットされるわけではありません。この区別は、日付入力を受け入れるAPIを設計する際に重要です。
formatRangeを使った日付範囲
日付範囲を表示するには、formatRange()を使用します:
const start = new Date(2025, 5, 27);
const end = new Date(2025, 6, 3);
new Intl.DateTimeFormat('en-US', { dateStyle: 'medium' })
.formatRange(start, end);
// "Jun 27 – Jul 3, 2025"
フォーマッターは、ロケールの慣習に基づいて冗長な部分をインテリジェントに省略します。
Discover how at OpenReplay.com.
Intl.NumberFormatの丸めと表示制御
最新の丸めオプション
Intl.NumberFormatの丸め動作は、最近の仕様で大幅に拡張されましたが、サポート状況はランタイムによって異なります。minimumFractionDigitsとmaximumFractionDigitsに加えて、以下のオプションが利用可能になりました:
roundingMode: 値の丸め方を制御します(ceil、floor、halfExpand、halfEvenなど)roundingIncrement: 特定の増分(5、10、50など)に丸めますtrailingZeroDisplay: 末尾のゼロを表示するかどうかを制御します(autoまたはstripIfInteger)
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
roundingMode: 'halfEven',
maximumFractionDigits: 2
});
formatter.format(2.225); // "$2.22" (銀行家の丸め)
formatter.format(2.235); // "$2.24"
ロケール対応の数値フォーマットパターン
コンパクト表記と単位フォーマットの場合:
// コンパクト表記
new Intl.NumberFormat('en-US', {
notation: 'compact',
compactDisplay: 'short'
}).format(1234567); // "1.2M"
// 単位フォーマット
new Intl.NumberFormat('de-DE', {
style: 'unit',
unit: 'kilometer-per-hour',
unitDisplay: 'short'
}).format(120); // "120 km/h"
実用的な考慮事項
フォーマッターインスタンスの再利用
フォーマッターの作成にはオーバーヘッドがあります。複数の値をフォーマットする場合はキャッシュしてください:
// 推奨
const priceFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
prices.map(p => priceFormatter.format(p));
// 非推奨
prices.map(p => new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(p));
機能検出
新しいオプションをプログラム的にチェックします:
try {
new Intl.NumberFormat('en', { roundingMode: 'halfEven' });
// 機能がサポートされています
} catch (e) {
// デフォルトの丸めにフォールバック
}
ロケールとタイムゾーン
これらは独立した概念です。ロケール(en-GB)はフォーマット規則を決定します。タイムゾーン(Europe/London)は表示される時刻を決定します。東京時間を表示しながら、ドイツの規則で日付をフォーマットすることができます。
まとめ
日付フォーマットとロケール対応の数値フォーマットのためのIntl APIは大幅に成熟しました。仕様には、外部ライブラリが必要な理由のほとんどを排除する丸めモード、表示制御、範囲フォーマットが含まれるようになりました。
Temporal型はIntlと並行して存在します。Temporal型は独自のtoLocaleString()呼び出しを処理し、Intl.DateTimeFormatは引き続きDateオブジェクトをフォーマットします。この分離を中心にメンタルモデルを構築し、ハードコーディングされた文字列の期待値なしでテストし、パフォーマンスのためにフォーマッターインスタンスを再利用してください。
よくある質問
いいえ。Intl.DateTimeFormat.prototype.format()はDateオブジェクトのみを受け入れます。PlainDateやZonedDateTimeなどのTemporal型には独自のtoLocaleString()メソッドがあります。内部的に類似のロケールデータを使用する場合がありますが、Intl.DateTimeFormat自体によってフォーマットされるわけではありません。
IntlはランタイムごとにバンドルされているICUロケールデータに依存しています。Chrome、Safari、Firefox、Node.jsは異なるICUバージョンを搭載している可能性があり、出力にわずかな違いが生じます。テストで期待される文字列をハードコーディングしないでください。フォーマット動作を確実に検証するには、formatToParts()または構造的なアサーションを使用してください。
銀行家の丸め(halfEven)は、中間値を最も近い偶数桁に丸めることで、累積的な丸め誤差を減らします。金融や会計の文脈で一般的に使用されますが、正確な結果は二進浮動小数点表現の影響を受ける可能性があります。
オプションを有効にしてフォーマッターの構築を試みてください。ランタイムがサポートしていない場合、エンジンによってはRangeErrorをスローするか、オプションを黙って無視する可能性があります。常にフォールバック戦略を含めてください。
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.