利用可能な暦体系のリストを取得する

JavaScript環境がサポートする暦体系を確認する

はじめに

グローバルなユーザー向けのアプリケーションを構築する際、ユーザーは自分が好む暦体系で日付を表示したいと考えることがよくあります。西洋諸国で使用されているグレゴリオ暦には馴染みがあるかもしれませんが、多くの文化圏ではイスラム暦(ヒジュラ暦)、ヘブライ暦、仏暦など、まったく異なる暦体系を使用しています。

ユーザーが好みの暦を選択できるようにするには、JavaScript環境がどの暦体系をサポートしているかを把握する必要があります。古くなったり、サポートされていない値を含む可能性のあるハードコードされたリストを維持するのではなく、JavaScriptは実行時に利用可能な暦体系を検出するメソッドを提供しています。

Intl.supportedValuesOf()メソッドに"calendar"パラメータを指定すると、現在の環境でサポートされているすべての暦体系識別子の配列が返されます。これにより、アプリケーションは正しく動作する暦オプションのみを提供できます。

暦体系とは

暦体系とは、時間を整理し計算するためのさまざまな方法です。すべての暦は日、月、年を追跡しますが、年の始まりの計算方法、月の長さ、天文学的周期の考慮方法については異なる規則を使用しています。

1582年に導入されたグレゴリオ暦は、最も広く使用されている民間暦です。約365.25日の太陽年を使用し、12か月に分割され、4年ごとに閏年が追加されます(世紀年には例外があります)。

他の暦体系は異なる規則に従っています。イスラム暦は純粋な太陰暦で、月は月の満ち欠けに従います。つまり、イスラム暦の1年は太陽年より約11日短く、イスラム暦の日付は時間の経過とともに季節を通じて移動します。ヘブライ暦と中国暦は太陰太陽暦で、太陰月と太陽年に暦を同期させるための調整を組み合わせています。

JavaScriptで日付をフォーマットする際、暦法システムによって表示される年、月、日の数値が決まります。同じ時点でも、異なる暦法システムでは異なる日付表現になります。

Intl.supportedValuesOfを使用して暦法システムを取得する

Intl.supportedValuesOf()メソッドは、返す値のタイプを指定する文字列パラメータを受け取ります。暦法システムを取得するには、"calendar"を渡します。

const calendars = Intl.supportedValuesOf("calendar");
console.log(calendars);
// Output: ["buddhist", "chinese", "coptic", "dangi", "ethioaa",
//          "ethiopic", "gregory", "hebrew", "indian", "islamic",
//          "islamic-civil", "islamic-rgsa", "islamic-tbla",
//          "islamic-umalqura", "iso8601", "japanese", "persian", "roc"]

このメソッドは、暦法識別子を表す文字列の配列を返します。これらの識別子はUnicode CLDR(Common Locale Data Repository)標準に従っており、異なるプラットフォームやプログラミング言語間で暦法システムを参照する一貫した方法を提供します。

返される配列には次の特性があります。

  • 値は昇順のアルファベット順にソートされます
  • 重複する値は削除されます
  • 各識別子は小文字とハイフンを使用します
  • リストにはJavaScript実装でサポートされているすべての暦法システムが含まれます

ブラウザやJavaScript環境によってサポートされる暦法のセットは異なりますが、すべての最新ブラウザは一般的な暦法のコアセットをサポートしています。

暦法識別子を理解する

各暦法識別子は、1つ以上の文化で使用される特定の暦法システムを表します。最も一般的な識別子は次のとおりです。

"gregory"識別子は、ほとんどの国で使用されている標準的な民間暦であるグレゴリオ暦を表します。これは、米国、ヨーロッパ、またはその他のほとんどの地域に住んでいる場合に日常生活で使用する暦です。

"buddhist"識別子は、タイ仏暦を表します。これはグレゴリオ暦と同じ月と日の構造を使用しますが、仏陀の誕生(グレゴリオ暦で紀元前543年)から年を数えます。グレゴリオ暦の2025年は、仏暦では2568年です。

"chinese"識別子は、伝統的な中国暦を表します。これは太陰太陽暦で、月は月の満ち欠けに従い、年は木星の公転周期に合わせられています。中国暦は、春節などの伝統的な祝日を決定するために使用されます。

"islamic"識別子は、イスラム暦(ヒジュラ暦)を表します。これは純粋な太陰暦で、29日または30日の12か月で構成されています。年は、西暦622年にムハンマドがメッカからメディナに移住したヒジュラから数えられます。

"hebrew"識別子は、ユダヤ教の宗教的儀式に使用されるヘブライ暦を表します。これは太陰太陽暦で、伝統的な天地創造の日(グレゴリオ暦で紀元前3761年)から年を数えます。

"japanese"識別子は、和暦を表します。和暦はグレゴリオ暦と同じ月日構造を使用しますが、在位する天皇に基づいて時代を区分します。現在の元号は2019年に始まった令和です。

"persian"識別子は、イランとアフガニスタンで使用される太陽ヒジュラ暦を表します。これは太陽暦で、ヒジュラから年を数えるため、太陰イスラム暦とは異なります。

その他の識別子には、"coptic"(コプト正教会暦)、"dangi"(伝統的な韓国暦)、"ethiopic"(エチオピア暦)、"indian"(インド国定暦)、"roc"(台湾で使用される中華民国暦)があります。

一部の識別子には、"islamic-civil""islamic-rgsa""islamic-tbla""islamic-umalqura"などのバリアントがあり、これらはイスラム暦の異なる計算方法を表します。

"iso8601"識別子は、ISO 8601暦を表します。これは基本的にグレゴリオ暦ですが、常に先発グレゴリオ暦(1582年の導入以前にグレゴリオ暦を遡及適用したもの)を使用します。

カレンダーシステムの動作を確認する

カレンダーシステムが日付フォーマットにどのように影響するかを理解するために、異なるカレンダーを使用して同じ日付をフォーマットします。

const date = new Date("2025-10-15");

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

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

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

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

console.log(gregorian.format(date));
// Output: "October 15, 2025"

console.log(islamic.format(date));
// Output: "Rabi' II 16, 1447 AH"

console.log(hebrew.format(date));
// Output: "Tishrei 23, 5786"

console.log(buddhist.format(date));
// Output: "October 15, 2568 BE"

同じJavaScriptのDateオブジェクトは同じ時点を表していますが、各カレンダーシステムはその時点を異なる年、月、日の値で表現します。グレゴリオ暦の2025年10月15日は、イスラム暦では1447年ラビーウ・アッサーニー16日、ヘブライ暦では5786年ティシュレー23日、仏暦では2568年10月15日に相当します。

これは、異なるカレンダーに従うユーザーのために日付をフォーマットする際に、どのカレンダーシステムを使用するかを指定する必要がある理由を示しています。

カレンダーセレクターの構築

ユーザーが希望するカレンダーを選択できるユーザーインターフェースを作成する場合、利用可能なカレンダーをクエリして、セレクターを動的に構築します。

function buildCalendarSelector() {
  const calendars = Intl.supportedValuesOf("calendar");
  const select = document.createElement("select");
  select.id = "calendar-selector";

  calendars.forEach(calendar => {
    const option = document.createElement("option");
    option.value = calendar;
    option.textContent = calendar;
    select.appendChild(option);
  });

  return select;
}

const selector = buildCalendarSelector();
document.body.appendChild(selector);

これにより、サポートされているすべてのカレンダーを含むドロップダウンが作成されます。ただし、"gregory""islamic"のような技術的な識別子は、ユーザーフレンドリーではありません。ユーザーは自分の言語で説明的な名前を見る必要があります。

人間が読めるカレンダー名の表示

Intl.DisplayNames APIは、カレンダー識別子を人間が読める名前に変換します。これを使用して、より良いカレンダーセレクターを作成します。

function buildCalendarSelector(locale = "en-US") {
  const calendars = Intl.supportedValuesOf("calendar");
  const displayNames = new Intl.DisplayNames([locale], { type: "calendar" });

  const select = document.createElement("select");
  select.id = "calendar-selector";

  calendars.forEach(calendar => {
    const option = document.createElement("option");
    option.value = calendar;
    option.textContent = displayNames.of(calendar);
    select.appendChild(option);
  });

  return select;
}

const selector = buildCalendarSelector("en-US");
document.body.appendChild(selector);

これで、ドロップダウンには技術的な識別子の代わりに「グレゴリオ暦」、「イスラム暦」、「ヘブライ暦」などの名前が表示されます。

ロケールを変更することで、カレンダー名を異なる言語で表示できます。

const calendars = ["gregory", "islamic", "hebrew", "buddhist", "chinese"];

const englishNames = new Intl.DisplayNames(["en-US"], { type: "calendar" });
const frenchNames = new Intl.DisplayNames(["fr-FR"], { type: "calendar" });
const arabicNames = new Intl.DisplayNames(["ar-SA"], { type: "calendar" });

calendars.forEach(calendar => {
  console.log(`${calendar}:`);
  console.log(`  English: ${englishNames.of(calendar)}`);
  console.log(`  French: ${frenchNames.of(calendar)}`);
  console.log(`  Arabic: ${arabicNames.of(calendar)}`);
});

// Output:
// gregory:
//   English: Gregorian Calendar
//   French: calendrier grégorien
//   Arabic: التقويم الميلادي
// islamic:
//   English: Islamic Calendar
//   French: calendrier musulman
//   Arabic: التقويم الهجري
// ...

これにより、ユーザーは希望する言語でカレンダー名を見ることができます。

特定のカレンダーがサポートされているかを確認する

アプリケーションでカレンダーを使用する前に、JavaScript環境がそれをサポートしているかどうかを確認します。これにより、ランタイムエラーを防ぎ、フォールバック動作を実装できます。

function isCalendarSupported(calendar) {
  const supported = Intl.supportedValuesOf("calendar");
  return supported.includes(calendar);
}

if (isCalendarSupported("islamic")) {
  const formatter = new Intl.DateTimeFormat("en-US", {
    calendar: "islamic",
    year: "numeric",
    month: "long",
    day: "numeric"
  });
  console.log(formatter.format(new Date()));
} else {
  console.log("Islamic calendar is not supported");
}

このパターンは、任意のカレンダー識別子に対して機能します。ユーザーインターフェースでカレンダーオプションの表示・非表示を切り替えたり、優先カレンダーが利用できない場合にデフォルトカレンダーにフォールバックしたりする際に使用できます。

再利用可能な機能検出関数の作成

カレンダーのサポート状況を確認し、欠落しているカレンダーを適切に処理する汎用関数を構築します。

function getCalendarOrFallback(preferredCalendar, fallbackCalendar = "gregory") {
  const supported = Intl.supportedValuesOf("calendar");

  if (supported.includes(preferredCalendar)) {
    return preferredCalendar;
  }

  if (supported.includes(fallbackCalendar)) {
    return fallbackCalendar;
  }

  return supported[0];
}

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

console.log(formatter.format(new Date()));

この関数は、優先カレンダーの使用を試み、優先カレンダーが利用できない場合は指定されたフォールバックカレンダーにフォールバックし、最終的に両方がサポートされていない場合は最初に利用可能なカレンダーを返します。

アプリケーション用のカレンダーのフィルタリング

ほとんどのアプリケーションは、すべてのカレンダーシステムをサポートする必要はありません。ユーザーに関連するカレンダーのみを含むようにリストをフィルタリングします。

function getRelevantCalendars(allowedCalendars) {
  const supported = Intl.supportedValuesOf("calendar");
  return allowedCalendars.filter(calendar => supported.includes(calendar));
}

const allowedCalendars = ["gregory", "islamic", "hebrew", "buddhist"];
const availableCalendars = getRelevantCalendars(allowedCalendars);

console.log(availableCalendars);
// Output: ["gregory", "islamic", "hebrew", "buddhist"]
// (assuming all are supported in the current environment)

これにより、要件を満たし、かつユーザーのブラウザで動作するカレンダーのみを提供できます。

これをカレンダー選択と組み合わせて、焦点を絞ったユーザーインターフェースを作成できます。

function buildFilteredCalendarSelector(allowedCalendars, locale = "en-US") {
  const supported = Intl.supportedValuesOf("calendar");
  const available = allowedCalendars.filter(cal => supported.includes(cal));
  const displayNames = new Intl.DisplayNames([locale], { type: "calendar" });

  const select = document.createElement("select");

  available.forEach(calendar => {
    const option = document.createElement("option");
    option.value = calendar;
    option.textContent = displayNames.of(calendar);
    select.appendChild(option);
  });

  return select;
}

const selector = buildFilteredCalendarSelector(
  ["gregory", "islamic", "hebrew", "buddhist"],
  "en-US"
);
document.body.appendChild(selector);

これにより、サポートしたいカレンダーのみを表示し、人間が読める名前で、現在の環境で動作することが保証されたセレクターが作成されます。

カテゴリ別のカレンダーのグループ化

多数のカレンダーをサポートするアプリケーションの場合、タイプや地域別にグループ化して使いやすさを向上させます。

function groupCalendars() {
  const calendars = Intl.supportedValuesOf("calendar");

  const groups = {
    solar: ["gregory", "iso8601", "persian", "ethiopic", "coptic"],
    lunar: ["islamic", "islamic-civil", "islamic-rgsa", "islamic-tbla", "islamic-umalqura"],
    lunisolar: ["hebrew", "chinese", "dangi"],
    erasBased: ["japanese", "roc", "buddhist"],
    other: []
  };

  const grouped = {
    solar: [],
    lunar: [],
    lunisolar: [],
    erasBased: [],
    other: []
  };

  calendars.forEach(calendar => {
    let placed = false;

    for (const [group, members] of Object.entries(groups)) {
      if (members.includes(calendar)) {
        grouped[group].push(calendar);
        placed = true;
        break;
      }
    }

    if (!placed) {
      grouped.other.push(calendar);
    }
  });

  return grouped;
}

const grouped = groupCalendars();
console.log(grouped);
// Output:
// {
//   solar: ["gregory", "iso8601", "persian", "ethiopic", "coptic"],
//   lunar: ["islamic", "islamic-civil", "islamic-rgsa", ...],
//   lunisolar: ["hebrew", "chinese", "dangi"],
//   erasBased: ["japanese", "roc", "buddhist"],
//   other: ["ethioaa", "indian"]
// }

この構成により、ユーザーは異なるカレンダーシステムの特性を理解し、必要なものを見つけやすくなります。

サポートされていない環境への対応

Intl.supportedValuesOf()メソッドは2022年にJavaScriptに追加されました。古いブラウザはこれをサポートしていません。使用する前にメソッドが存在するかどうかを確認してください。

function getCalendars() {
  if (typeof Intl.supportedValuesOf === "function") {
    return Intl.supportedValuesOf("calendar");
  }

  return ["gregory"];
}

const calendars = getCalendars();
console.log(calendars);

これは、最新のブラウザでサポートされている暦の完全なリストを返し、古い環境ではグレゴリオ暦のみにフォールバックします。

より良い後方互換性のために、より完全なフォールバックリストを提供してください。

function getCalendars() {
  if (typeof Intl.supportedValuesOf === "function") {
    return Intl.supportedValuesOf("calendar");
  }

  return [
    "buddhist",
    "chinese",
    "coptic",
    "ethiopic",
    "gregory",
    "hebrew",
    "indian",
    "islamic",
    "japanese",
    "persian",
    "roc"
  ];
}

これにより、古いブラウザに対して妥当な暦のセットが提供されますが、すべての環境ですべての暦が動作することを保証することはできません。

暦とロケールの違いを理解する

暦とロケールは関連していますが、別々の概念です。ロケールは言語と地域の書式規則を決定し、暦はどの暦システムを使用するかを決定します。

1つのロケールで複数の暦を使用できます。たとえば、サウジアラビアのアラビア語話者は、通常、宗教的な目的にはイスラム暦を使用し、民事目的にはグレゴリオ暦を使用します。どちらの暦を使用してもアラビア語で日付をフォーマットできます。

const date = new Date("2025-10-15");

const arabicGregorian = new Intl.DateTimeFormat("ar-SA", {
  calendar: "gregory",
  year: "numeric",
  month: "long",
  day: "numeric"
});

const arabicIslamic = new Intl.DateTimeFormat("ar-SA", {
  calendar: "islamic",
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(arabicGregorian.format(date));
// Output: "١٥ أكتوبر ٢٠٢٥"

console.log(arabicIslamic.format(date));
// Output: "١٦ ربيع الآخر ١٤٤٧ هـ"

どちらもアラビア語のテキストと数字を使用していますが、それぞれの暦に従って異なる日付を表示します。

逆に、同じ暦を異なる言語でフォーマットすることもできます。

const date = new Date("2025-10-15");

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

const arabicIslamic = new Intl.DateTimeFormat("ar-SA", {
  calendar: "islamic",
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(englishIslamic.format(date));
// Output: "Rabi' II 16, 1447 AH"

console.log(arabicIslamic.format(date));
// Output: "١٦ ربيع الآخر ١٤٤٧ هـ"

どちらもイスラム暦の日付を表示していますが、異なる言語と数字システムを使用しています。

利用可能な暦を照会するタイミング

次の状況で利用可能な暦を照会します。

暦選択インターフェースを構築する場合、Intl.supportedValuesOf("calendar")を呼び出してオプションを設定します。これにより、現在の環境で動作する暦のみが表示されます。

暦ベースの機能を実装する場合、使用する前に必要な暦がサポートされているかどうかを確認します。これによりエラーを防ぎ、代替暦への適切なフォールバックが可能になります。

ユーザー設定を保存する場合、サポートされているリストに対して暦の選択を検証します。これにより、保存された設定が異なるデバイスやブラウザ間で有効なままであることが保証されます。

環境間で移行する際は、移行元と移行先の両方の環境でカレンダーのサポート状況を確認してください。カレンダーのサポートは、ブラウザのバージョン、Node.jsのバージョン、および異なるJavaScriptランタイムによって異なる場合があります。

カレンダーに依存するデータを読み込む際は、日付の解析やフォーマットを試みる前に、カレンダーが利用可能であることを確認してください。これにより、特定のカレンダーシステムで日付を扱う際のランタイムエラーを防ぐことができます。