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は次の時間単位をサポートしています:yearsmonthsweeksdayshoursminutessecondsmillisecondsmicrosecondsnanoseconds。データに合った単位を使用してください。

フォーマットスタイルを選択する

styleオプションは出力の密度を制御します。4つのスタイルが利用可能です:longshortnarrowdigital

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"

表示する単位を制御する

デフォルトでは、フォーマッターは期間オブジェクトで指定したすべての単位を表示します。オプションオブジェクトを使用して単位の表示を制御できます。

各単位の表示スタイルを個別に指定します。オプションはlongshortnarrownumeric2-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);

各パーツにはtypevalueがあります。タイプにはhourminutesecondliteral、およびhourUnitminuteUnitなどの単位ラベルが含まれます。

[
  { 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は期間をフォーマットするものであり、時間計算を行うものではありません。日付、タイムスタンプ、その他のソースから期間の値を自分で計算する必要があります。フォーマッターは表示のみを処理します。