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つのスタイルが利用可能です:longshortnarrow、および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時間、46分、40秒
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の期間フォーマットを置き換える:

// 移行前: Moment.js
const duration = moment.duration(6400, 'seconds');
const formatted = duration.hours() + 'h ' + duration.minutes() + 'm';

// 移行後: 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の期間フォーマットを置き換える:

// 移行前: date-fns
import { formatDuration, intervalToDuration } from 'date-fns';
const duration = intervalToDuration({ start: 0, end: 6400000 });
const formatted = formatDuration(duration);

// 移行後: 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 {
  // 古いブラウザ向けのフォールバック
  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は時間計算ではなく、継続時間をフォーマットします。日付、タイムスタンプ、またはその他のソースから継続時間の値を自分で計算する必要があります。フォーマッタは表示のみを処理します。