Intl.DurationFormat API
JavaScriptで自動ローカライゼーションによる時間の長さのフォーマット
はじめに
何かにかかる時間を表示する際、ユーザー向けに期間の値をフォーマットする必要があります。動画プレーヤーは再生時間を表示し、フライト予約は移動時間を表示し、フィットネスアプリはワークアウトの時間を表示します。ローカライゼーションがない場合、次のようなコードを書くかもしれません。
const hours = 1;
const minutes = 46;
const seconds = 40;
const duration = `${hours}h ${minutes}m ${seconds}s`;
これは言語に関係なく、すべてのユーザーに対して「1h 46m 40s」を生成します。フランス語ユーザーは「1 h 46 min 40 s」を期待しているのに「1h 46m 40s」が表示されます。ドイツ語ユーザーも同じ英語の略語が表示されます。スペイン語ユーザーは単位間に「y」接続詞がありません。
Intl.DurationFormat APIはこれを解決します。外部ライブラリや手動での文字列構築なしに、ユーザーの言語と文化的慣習に従って時間の長さをフォーマットします。
const duration = { hours: 1, minutes: 46, seconds: 40 };
new Intl.DurationFormat('fr-FR', { style: 'short' }).format(duration);
// "1 h, 46 min et 40 s"
フォーマッターは、任意のロケールに対して略語、接続詞、語順、スペーシングを自動的に処理します。
期間とは
期間は時間の経過を表し、時点を表すものではありません。日付と時刻は何かが起こるタイミングを示します。期間は何かにかかる時間を測定します。
この区別はフォーマットにおいて重要です。日付にはカレンダー、タイムゾーン、歴史的なルールが関係します。期間はよりシンプルで、カレンダーのコンテキストなしに標準単位で経過時間を測定します。
Intl.DurationFormat APIは期間を処理します。Intl.DateTimeFormat APIは日付と時刻を処理します。それぞれの用途に適したツールを使用してください。
期間フォーマッターの作成
コンストラクターはロケールとオプションオブジェクトを受け取ります。ロケールは出力言語を決定します。オプションはフォーマットスタイルと単位の表示を制御します。
const formatter = new Intl.DurationFormat('en', { style: 'long' });
format()を期間オブジェクトで呼び出します。オブジェクトには時間単位の数値プロパティが含まれます。表示したい単位のみを含めてください。
const duration = { hours: 2, minutes: 30 };
formatter.format(duration);
// "2 hours and 30 minutes"
このAPIは次の時間単位をサポートしています:years、months、weeks、days、hours、minutes、seconds、milliseconds、microseconds、nanoseconds。データに合った単位を使用してください。
フォーマットスタイルを選択する
styleオプションは出力の密度を制御します。4つのスタイルが利用可能です:long、short、narrow、digital。
longスタイルは完全な単語を使用します。文章やアクセシビリティのために使用してください。
const duration = { hours: 1, minutes: 46, seconds: 40 };
new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "1 hour, 46 minutes and 40 seconds"
shortスタイルは一般的な略語を使用します。スペースが限られているが可読性が重要な場合に使用してください。
new Intl.DurationFormat('en', { style: 'short' }).format(duration);
// "1 hr, 46 min and 40 sec"
narrowスタイルは最小限の文字を使用します。モバイルインターフェースなどのコンパクトな表示に使用してください。
new Intl.DurationFormat('en', { style: 'narrow' }).format(duration);
// "1h 46m 40s"
digitalスタイルはコロンを使用したタイマーのような出力を生成します。メディアプレーヤーやカウントダウン表示に使用してください。
new Intl.DurationFormat('en', { style: 'digital' }).format(duration);
// "1:46:40"
言語間のローカライゼーション
同じ期間でも各言語で異なる形式になります。APIはすべてのローカライゼーションを自動的に処理します。
const duration = { hours: 1, minutes: 46, seconds: 40 };
new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "1 hour, 46 minutes and 40 seconds"
new Intl.DurationFormat('fr', { style: 'long' }).format(duration);
// "1 heure, 46 minutes et 40 secondes"
new Intl.DurationFormat('de', { style: 'long' }).format(duration);
// "1 Stunde, 46 Minuten und 40 Sekunden"
new Intl.DurationFormat('es', { style: 'long' }).format(duration);
// "1 hora, 46 minutos y 40 segundos"
new Intl.DurationFormat('ja', { style: 'long' }).format(duration);
// "1時間46分40秒"
各ロケールが異なる単語、略語、接続詞を使用していることに注目してください。フランス語は「et」、ドイツ語は「und」、スペイン語は「y」を使用し、日本語は接続詞を使用しません。APIはこれらのルールを理解しています。
shortスタイルとnarrowスタイルも正しくローカライズされます。
new Intl.DurationFormat('fr', { style: 'short' }).format(duration);
// "1 h, 46 min et 40 s"
new Intl.DurationFormat('de', { style: 'narrow' }).format(duration);
// "1 Std. 46 Min. 40 Sek."
時間計算から期間オブジェクトを構築する
期間オブジェクトは時間単位プロパティを持つプレーンなJavaScriptオブジェクトです。任意の時間計算から作成できます。
ミリ秒を適切な係数で除算して期間単位に変換します。
const milliseconds = 6400000; // 1 hour, 46 minutes, 40 seconds
const hours = Math.floor(milliseconds / 3600000);
const minutes = Math.floor((milliseconds % 3600000) / 60000);
const seconds = Math.floor((milliseconds % 60000) / 1000);
const duration = { hours, minutes, seconds };
new Intl.DurationFormat('en', { style: 'short' }).format(duration);
// "1 hr, 46 min and 40 sec"
2つの日付からタイムスタンプを減算して期間を計算します。
const start = new Date('2025-01-01T10:00:00');
const end = new Date('2025-01-01T11:46:40');
const diffMs = end - start;
const hours = Math.floor(diffMs / 3600000);
const minutes = Math.floor((diffMs % 3600000) / 60000);
const seconds = Math.floor((diffMs % 60000) / 1000);
const duration = { hours, minutes, seconds };
必要な単位のみを含めてください。表示したい場合を除き、ゼロ値は省略してください。
const duration = { minutes: 5, seconds: 30 };
new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "5 minutes and 30 seconds"
表示する単位を制御する
デフォルトでは、フォーマッターは期間オブジェクトで指定したすべての単位を表示します。オプションオブジェクトを使用して単位の表示を制御できます。
各単位の表示スタイルを個別に指定します。オプションはlong、short、narrow、numeric、2-digitです。
const duration = { hours: 1, minutes: 5, seconds: 3 };
new Intl.DurationFormat('en', {
hours: 'long',
minutes: 'numeric',
seconds: '2-digit'
}).format(duration);
// "1 hour, 5:03"
numericスタイルはラベルなしで数値を表示します。2-digitスタイルはゼロパディングを追加します。テキストと数値を組み合わせたコンパクトな表示に使用します。
期間オブジェクトとオプションの両方から単位を省略することで、単位を非表示にできます。
const duration = { hours: 2, minutes: 30 };
new Intl.DurationFormat('en', { style: 'short' }).format(duration);
// "2 hr and 30 min"
デジタルスタイルでは、最大から最小までのすべての単位が存在するか、明示的に設定されている必要があります。
const duration = { minutes: 5, seconds: 30 };
new Intl.DurationFormat('en', { style: 'digital' }).format(duration);
// "5:30"
動画プレーヤーの再生時間をフォーマットする
動画プレーヤーはコントロール部分に再生時間を表示します。コンパクトな表示にはナロースタイルまたはデジタルスタイルを使用します。
function formatVideoDuration(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
const duration = hours > 0
? { hours, minutes, seconds: secs }
: { minutes, seconds: secs };
const locale = navigator.language;
return new Intl.DurationFormat(locale, { style: 'digital' }).format(duration);
}
formatVideoDuration(6400); // "1:46:40"
formatVideoDuration(330); // "5:30"
これにより、時間を条件付きで含めることで、短い動画と長い動画の両方に対応できます。
フライトと移動時間をフォーマットする
旅行予約インターフェースは移動時間を表示します。限られたスペースでの可読性を高めるためにショートスタイルを使用します。
function formatFlightDuration(departureDate, arrivalDate, locale) {
const diffMs = arrivalDate - departureDate;
const hours = Math.floor(diffMs / 3600000);
const minutes = Math.floor((diffMs % 3600000) / 60000);
const duration = { hours, minutes };
return new Intl.DurationFormat(locale, { style: 'short' }).format(duration);
}
const departure = new Date('2025-06-15T10:30:00');
const arrival = new Date('2025-06-15T18:45:00');
formatFlightDuration(departure, arrival, 'en');
// "8 hr and 15 min"
formatFlightDuration(departure, arrival, 'fr');
// "8 h et 15 min"
ワークアウトとアクティビティの時間をフォーマットする
フィットネスアプリは運動時間を記録します。セッションサマリーにはロングスタイルを、リストビューにはナロースタイルを使用します。
function formatWorkoutDuration(startTime, endTime, locale) {
const diffMs = endTime - startTime;
const hours = Math.floor(diffMs / 3600000);
const minutes = Math.floor((diffMs % 3600000) / 60000);
const duration = hours > 0
? { hours, minutes }
: { minutes };
return new Intl.DurationFormat(locale, { style: 'long' }).format(duration);
}
const workoutStart = new Date('2025-06-15T07:00:00');
const workoutEnd = new Date('2025-06-15T08:15:00');
formatWorkoutDuration(workoutStart, workoutEnd, 'en');
// "1 hour and 15 minutes"
カスタム表示用にフォーマット済みパーツを取得する
formatToParts()メソッドは、フォーマット済み出力の各部分を表すオブジェクトの配列を返します。これを使用して個々のパーツをスタイル設定したり、カスタムレイアウトを構築したりできます。
const duration = { hours: 1, minutes: 46, seconds: 40 };
const parts = new Intl.DurationFormat('en', { style: 'long' }).formatToParts(duration);
各パーツにはtypeとvalueがあります。タイプにはhour、minute、second、literal、およびhourUnit、minuteUnitなどの単位ラベルが含まれます。
[
{ type: "integer", value: "1" },
{ type: "literal", value: " " },
{ type: "unit", value: "hour" },
{ type: "literal", value: ", " },
{ type: "integer", value: "46" },
{ type: "literal", value: " " },
{ type: "unit", value: "minutes" },
{ type: "literal", value: " and " },
{ type: "integer", value: "40" },
{ type: "literal", value: " " },
{ type: "unit", value: "seconds" }
]
パーツをマップしてカスタムスタイリングを適用します。
function StyledDuration({ duration }) {
const parts = new Intl.DurationFormat('en', { style: 'long' }).formatToParts(duration);
return parts.map((part, i) => {
if (part.type === 'integer') {
return <strong key={i}>{part.value}</strong>;
}
return <span key={i}>{part.value}</span>;
});
}
パフォーマンスのためにフォーマッターインスタンスを再利用する
各期間に対して新しいフォーマッターを作成するとオーバーヘッドが発生します。フォーマッターを一度作成して再利用してください。
const formatter = new Intl.DurationFormat('en', { style: 'short' });
const durations = [
{ hours: 1, minutes: 30 },
{ hours: 2, minutes: 15 },
{ hours: 0, minutes: 45 }
];
durations.map(d => formatter.format(d));
// ["1 hr and 30 min", "2 hr and 15 min", "45 min"]
このパターンは、ループやレンダリングで多数の期間をフォーマットする際のパフォーマンスを向上させます。
手動文字列構築からの移行
手動の文字列連結をAPIに置き換えます。これによりコードが削減され、ローカライゼーションが追加されます。
変更前:
function formatDuration(hours, minutes) {
if (hours > 0 && minutes > 0) {
return `${hours}h ${minutes}m`;
} else if (hours > 0) {
return `${hours}h`;
} else {
return `${minutes}m`;
}
}
変更後:
function formatDuration(hours, minutes, locale) {
const duration = {};
if (hours > 0) duration.hours = hours;
if (minutes > 0) duration.minutes = minutes;
return new Intl.DurationFormat(locale, { style: 'narrow' }).format(duration);
}
APIバージョンはすべての条件ロジックを自動的に処理し、複数の言語をサポートします。
ライブラリからの移行
Moment.jsやdate-fnsなどのライブラリは期間フォーマット機能を提供しますが、バンドルサイズが増加します。ネイティブAPIはこの依存関係を排除します。
Moment.jsの期間フォーマットを置き換える:
// Before: Moment.js
const duration = moment.duration(6400, 'seconds');
const formatted = duration.hours() + 'h ' + duration.minutes() + 'm';
// After: Intl.DurationFormat
const duration = {
hours: Math.floor(6400 / 3600),
minutes: Math.floor((6400 % 3600) / 60)
};
new Intl.DurationFormat('en', { style: 'narrow' }).format(duration);
date-fnsの期間フォーマットを置き換える:
// Before: date-fns
import { formatDuration, intervalToDuration } from 'date-fns';
const duration = intervalToDuration({ start: 0, end: 6400000 });
const formatted = formatDuration(duration);
// After: Intl.DurationFormat
const ms = 6400000;
const duration = {
hours: Math.floor(ms / 3600000),
minutes: Math.floor((ms % 3600000) / 60000),
seconds: Math.floor((ms % 60000) / 1000)
};
new Intl.DurationFormat('en', { style: 'long' }).format(duration);
ネイティブAPIは依存関係なしで同じ機能を提供します。
ブラウザサポートとポリフィル
Intl.DurationFormat APIは2025年3月にベースラインになりました。Chrome、Edge、Firefox、Safariの最新バージョンで動作します。
使用前にサポートを確認してください:
if (typeof Intl.DurationFormat !== 'undefined') {
const formatter = new Intl.DurationFormat('en', { style: 'short' });
return formatter.format(duration);
} else {
// Fallback for older browsers
return `${duration.hours}h ${duration.minutes}m`;
}
より広範なサポートが必要な場合は、ポリフィルを使用してください。FormatJSプロジェクトが@formatjs/intl-durationformatを提供しています。
npm install @formatjs/intl-durationformat
必要に応じてインポートしてポリフィルを適用します。
if (!Intl.DurationFormat) {
await import('@formatjs/intl-durationformat/polyfill');
}
ポリフィルはgzip圧縮で約15KBを追加します。モダンブラウザでのオーバーヘッドを避けるため、条件付きで読み込んでください。
DurationFormatを使用するタイミング
Intl.DurationFormatは、経過時間、残り時間、または期間の測定値を表示する際に使用します。これには、動画プレーヤー、タイマー、カウントダウン、トラッキングアプリ、旅行予約、セッション時間の表示などが含まれます。
日付、時刻、タイムスタンプには使用しないでください。それらにはIntl.DateTimeFormatを使用してください。「2時間前」のような相対時間表現には使用しないでください。それらにはIntl.RelativeTimeFormatを使用してください。
このAPIは期間をフォーマットするものであり、時間計算を行うものではありません。日付、タイムスタンプ、その他のソースから期間の値を自分で計算する必要があります。フォーマッターは表示のみを処理します。