JavaScriptでフォーマットされた日付の個々の部分を取得する方法

formatToParts()を使用してロケールでフォーマットされた日付の各コンポーネントに個別にアクセスする

はじめに

format()メソッドは「January 15, 2025」や「15/01/2025」のような完全にフォーマットされた文字列を返します。これは単純な表示には適していますが、個々の部分を異なるスタイルで装飾することはできません。月名を太字にしたり、年を異なる色で表示したり、特定の要素にカスタムマークアップを適用したりすることができません。

JavaScriptはこの問題を解決するためにformatToParts()メソッドを提供しています。単一の文字列を返す代わりに、フォーマットされた日付の各部分を表すオブジェクトの配列を返します。各部分にはmonthdayyearなどの型と、January152025などの値があります。これらの部分を処理して、カスタムスタイルを適用したり、複雑なレイアウトを構築したり、フォーマットされた日付をリッチなユーザーインターフェースに統合したりすることができます。

フォーマット済み文字列のカスタマイズが難しい理由

「January 15, 2025」のようなフォーマット済み文字列を受け取った場合、月がどこで終わり、日がどこから始まるかを簡単に識別することができません。ロケールによって要素の順序が異なります。一部のロケールでは異なる区切り文字を使用します。これらの文字列を確実に解析するには、Intl APIですでに実装されているフォーマットルールを複製する複雑なロジックが必要です。

月名を太字で表示するカレンダーアプリケーションを考えてみましょう。format()を使用する場合、以下のことが必要になります:

  1. どの文字が月名を表しているかを検出する
  2. 要素間のスペースや句読点を考慮する
  3. ロケール間で異なる月のフォーマットに対応する
  4. 日付を壊さないように文字列を慎重に解析する

このアプローチは脆弱でエラーが発生しやすいです。ロケールのフォーマットルールが変更されると、解析ロジックが破綻します。

formatToParts()メソッドは、コンポーネントを個別に提供することでこの問題を解消します。ロケールに関係なく、どの部分が何であるかを正確に示す構造化データを受け取ることができます。

formatToPartsを使用して日付コンポーネントを取得する

「formatToParts()」メソッドは戻り値を除いて「format()」と同じように動作します。同じオプションでフォーマッターを作成し、「format()」の代わりに「formatToParts()」を呼び出します。

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
console.log(parts);

これは以下のようなオブジェクトの配列を出力します:

[
  { type: "month", value: "January" },
  { type: "literal", value: " " },
  { type: "day", value: "15" },
  { type: "literal", value: ", " },
  { type: "year", value: "2025" }
]

各オブジェクトには、その部分が何を表すかを識別する「type」プロパティと、実際の文字列を含む「value」プロパティが含まれています。これらの部分はフォーマットされた出力と同じ順序で表示されます。

すべての値を結合することで、これを確認できます:

const formatted = parts.map(part => part.value).join("");
console.log(formatted);
// 出力: "January 15, 2025"

連結された部分は「format()」を呼び出した場合とまったく同じ出力を生成します。

パートタイプを理解する

「type」プロパティは各コンポーネントを識別します。異なるフォーマットオプションは異なるパートタイプを生成します。

基本的な日付フォーマットの場合:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
console.log(parts);
// [
//   { type: "month", value: "January" },
//   { type: "literal", value: " " },
//   { type: "day", value: "15" },
//   { type: "literal", value: ", " },
//   { type: "year", value: "2025" }
// ]

「month」タイプは月名または数字を表します。「day」タイプは月の日を表します。「year」タイプは年を表します。「literal」タイプはフォーマッターによって挿入されるスペース、句読点、またはその他のテキストを表します。

曜日付きの日付の場合:

const formatter = new Intl.DateTimeFormat("en-US", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
console.log(parts);
// [
//   { type: "weekday", value: "Wednesday" },
//   { type: "literal", value: ", " },
//   { type: "month", value: "January" },
//   { type: "literal", value: " " },
//   { type: "day", value: "15" },
//   { type: "literal", value: ", " },
//   { type: "year", value: "2025" }
// ]

「weekday」タイプは曜日を表します。

時間付きの日付の場合:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "numeric",
  minute: "numeric",
  second: "numeric"
});

const date = new Date(2025, 0, 15, 14, 30, 45);
const parts = formatter.formatToParts(date);
console.log(parts);
// [
//   { type: "month", value: "January" },
//   { type: "literal", value: " " },
//   { type: "day", value: "15" },
//   { type: "literal", value: ", " },
//   { type: "year", value: "2025" },
//   { type: "literal", value: " at " },
//   { type: "hour", value: "2" },
//   { type: "literal", value: ":" },
//   { type: "minute", value: "30" },
//   { type: "literal", value: ":" },
//   { type: "second", value: "45" },
//   { type: "literal", value: " " },
//   { type: "dayPeriod", value: "PM" }
// ]

「hour」、「minute」、「second」タイプは時間コンポーネントを表します。「dayPeriod」タイプは12時間形式でのAMまたはPMを表します。

日付の各部分にカスタムスタイルを適用する

formatToParts()の主な使用例は、異なるコンポーネントに異なるスタイルを適用することです。パーツ配列を処理して、特定のタイプをHTML要素でラップすることができます。

月名を太字にする例:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
const html = parts
  .map(part => {
    if (part.type === "month") {
      return `<strong>${part.value}</strong>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// 出力: "<strong>January</strong> 15, 2025"

このアプローチはあらゆるマークアップ言語で機能します。パーツ配列を処理することで、HTML、JSX、またはその他の形式を生成できます。

年を異なるスタイルにする例:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
const html = parts
  .map(part => {
    if (part.type === "year") {
      return `<span class="text-gray-500">${part.value}</span>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// 出力: "January 15, <span class="text-gray-500">2025</span>"

このパターンは、異なるコンポーネントに異なる視覚的な強調が必要なカレンダー表示でよく使用されます。

複数のスタイルを使用したカスタム日付表示の構築

複雑なインターフェースでは、複数のスタイル規則を組み合わせることがよくあります。異なるパーツタイプに対して同時に異なるクラスや要素を適用できます。

const formatter = new Intl.DateTimeFormat("en-US", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);

const html = parts
  .map(part => {
    switch (part.type) {
      case "weekday":
        return `<span class="weekday">${part.value}</span>`;
      case "month":
        return `<span class="month">${part.value}</span>`;
      case "day":
        return `<span class="day">${part.value}</span>`;
      case "year":
        return `<span class="year">${part.value}</span>`;
      case "literal":
        return `<span class="literal">${part.value}</span>`;
      default:
        return part.value;
    }
  })
  .join("");

console.log(html);
// 出力: "<span class="weekday">Wednesday</span><span class="literal">, </span><span class="month">January</span><span class="literal"> </span><span class="day">15</span><span class="literal">, </span><span class="year">2025</span>"

この細かい制御により、各コンポーネントに対して正確なスタイリングが可能になります。CSSを使用して各クラスを異なるスタイルにすることができます。

カスタム日付レイアウトの作成

標準のロケールフォーマットとは異なるカスタムレイアウトに日付コンポーネントを並べ替えることができます。特定の部分を抽出し、任意の順序で構成できます。

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);

const day = parts.find(p => p.type === "day").value;
const month = parts.find(p => p.type === "month").value;
const year = parts.find(p => p.type === "year").value;

const customLayout = `
  <div class="date-card">
    <div class="day-large">${day}</div>
    <div class="month-small">${month}</div>
    <div class="year-small">${year}</div>
  </div>
`;

console.log(customLayout);

これにより、日が目立つように表示され、その後に月と年が続く縦型カードレイアウトが作成されます。レイアウトが標準フォーマットと異なっていても、コンポーネントは適切にローカライズされたままです。

利用可能なすべてのパートタイプ

typeプロパティは、使用されるフォーマットオプションに応じて以下の値を持つことができます:

  • weekday:MondayやMonなどの曜日
  • era:BC、AD、BCEなどの時代表示
  • year:2025などの年
  • month:JanuaryやO1などの月名または数字
  • day:15などの月の日
  • dayPeriod:AMやPM、またはその他のロケール固有の日周期
  • hour:14や2などの時間
  • minute:30などの分
  • second:45などの秒
  • fractionalSecond:ミリ秒またはその他の小数部分の秒
  • timeZoneName:PSTやPacific Standard Timeなどのタイムゾーン名
  • literal:フォーマットによって追加されるスペース、句読点、またはその他のテキスト
  • relatedYear:代替カレンダーシステムにおけるグレゴリオ暦の年
  • yearName:一部のカレンダーシステムにおける名前付きの年
  • unknown:認識されないトークン

すべてのフォーマットオプションがすべてのパートタイプを生成するわけではありません。受け取るパーツは日付の値とフォーマッタの構成によって異なります。

時代表示のある日付:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  era: "short"
});

const date = new Date(-100, 0, 1);
const parts = formatter.formatToParts(date);
console.log(parts);
// [
//   { type: "year", value: "101" },
//   { type: "literal", value: " " },
//   { type: "era", value: "BC" }
// ]

タイムゾーンのある日付:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "numeric",
  minute: "numeric",
  timeZoneName: "short"
});

const date = new Date(2025, 0, 15, 14, 30);
const parts = formatter.formatToParts(date);
console.log(parts);
// Partsには{ type: "timeZoneName", value: "PST" }などが含まれます

小数部分の秒がある日付:

const formatter = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  second: "numeric",
  fractionalSecondDigits: 3
});

const date = new Date(2025, 0, 15, 14, 30, 45, 123);
const parts = formatter.formatToParts(date);
// Partsには{ type: "fractionalSecond", value: "123" }が含まれます

日付コンポーネントを条件付きでハイライトする

一部のアプリケーションでは、ビジネスロジックに基づいて特定の日付コンポーネントをハイライトします。formatToParts()を使用すると、適切なフォーマットを維持しながら、日付の値に基づいてスタイルを適用できます。

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

function formatDateWithHighlight(date) {
  const parts = formatter.formatToParts(date);
  const isWeekend = date.getDay() === 0 || date.getDay() === 6;

  const html = parts
    .map(part => {
      if (part.type === "day" && isWeekend) {
        return `<span class="text-blue-600 font-bold">${part.value}</span>`;
      }
      return part.value;
    })
    .join("");

  return html;
}

const saturday = new Date(2025, 0, 18);
console.log(formatDateWithHighlight(saturday));
// 出力: "January <span class="text-blue-600 font-bold">18</span>, 2025"

const monday = new Date(2025, 0, 13);
console.log(formatDateWithHighlight(monday));
// 出力: "January 13, 2025"

日付はロケールに適したフォーマットを受け取りながら、ビジネスロジックに基づいた条件付きスタイルが適用されます。

アクセシブルな日付表示の作成

formatToParts()を使用して、フォーマットされた日付にアクセシビリティ属性を追加できます。これにより、スクリーンリーダーが値を正確に読み上げることができます。

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

function formatAccessibleDate(date) {
  const parts = formatter.formatToParts(date);
  const formatted = parts.map(part => part.value).join("");
  const isoDate = date.toISOString().split('T')[0];

  return `<time datetime="${isoDate}">${formatted}</time>`;
}

const date = new Date(2025, 0, 15);
console.log(formatAccessibleDate(date));
// 出力: "<time datetime="2025-01-15">January 15, 2025</time>"

これにより、スクリーンリーダーが日付を適切に読み上げることができる一方で、表示はロケールに合わせたフォーマットで表示されます。

パーツがロケール固有のフォーマットを保持する方法

パーツ配列は、ロケール固有のフォーマットルールを自動的に維持します。異なるロケールでは、コンポーネントの順序や使用するフォーマットが異なりますが、formatToParts()はこれらの違いを処理します。

const usFormatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
console.log(usFormatter.formatToParts(date));
// [
//   { type: "month", value: "January" },
//   { type: "literal", value: " " },
//   { type: "day", value: "15" },
//   { type: "literal", value: ", " },
//   { type: "year", value: "2025" }
// ]

const ukFormatter = new Intl.DateTimeFormat("en-GB", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(ukFormatter.formatToParts(date));
// [
//   { type: "day", value: "15" },
//   { type: "literal", value: " " },
//   { type: "month", value: "January" },
//   { type: "literal", value: " " },
//   { type: "year", value: "2025" }
// ]

イギリスのフォーマットでは、日が月の前に配置されます。スタイリングコードはロケールに関係なく同じ方法でパーツ配列を処理し、フォーマットは自動的に適応します。

const jpFormatter = new Intl.DateTimeFormat("ja-JP", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(jpFormatter.formatToParts(date));
// [
//   { type: "year", value: "2025" },
//   { type: "literal", value: "年" },
//   { type: "month", value: "1月" },
//   { type: "day", value: "15" },
//   { type: "literal", value: "日" }
// ]

日本語のフォーマットでは、異なる順序と「年」や「日」などの文字リテラルが使用されます。パーツ配列はこれらのロケール固有の規則を自動的に反映します。

formatToPartsとフレームワークコンポーネントの組み合わせ

Reactのようなモダンフレームワークでは、formatToParts()を使用して効率的にコンポーネントを構築できます。

function DateDisplay({ date, locale, options }) {
  const formatter = new Intl.DateTimeFormat(locale, options);
  const parts = formatter.formatToParts(date);

  return (
    <span className="date-display">
      {parts.map((part, index) => {
        if (part.type === "month") {
          return <strong key={index}>{part.value}</strong>;
        }
        if (part.type === "year") {
          return <span key={index} className="text-sm text-gray-500">{part.value}</span>;
        }
        return <span key={index}>{part.value}</span>;
      })}
    </span>
  );
}

このコンポーネントは、どのロケールでも適切なフォーマットを維持しながら、異なる部分に異なるスタイルを適用します。

formatToPartsとformatを使い分ける場合

カスタマイズなしの単純なフォーマット済み文字列が必要な場合はformat()を使用してください。これは日付表示の一般的なケースです。

以下の場合はformatToParts()を使用してください:

  • 日付の異なる部分に異なるスタイルを適用する
  • フォーマット済みの日付でHTMLやJSXを構築する
  • 特定のコンポーネントに属性やメタデータを追加する
  • 日付コンポーネントをカスタムレイアウトに再配置する
  • フォーマット済みの日付を複雑なレイアウトに統合する
  • フォーマット済み出力をプログラムで処理する

formatToParts()メソッドは、単一の文字列ではなくオブジェクトの配列を作成するため、format()よりもわずかにオーバーヘッドが大きくなります。この差は一般的なアプリケーションでは無視できますが、1秒間に何千もの日付をフォーマットする場合は、format()の方がパフォーマンスが良くなります。

ほとんどのアプリケーションでは、パフォーマンスの懸念よりもスタイリングのニーズに基づいて選択してください。出力をカスタマイズする必要がない場合はformat()を使用してください。カスタムスタイルやマークアップが必要な場合はformatToParts()を使用してください。