Получите список доступных календарных систем

Узнайте, какие календарные системы поддерживает ваша среда JavaScript

Введение

При создании приложений для глобальной аудитории пользователи часто нуждаются в отображении дат в их предпочтительной системе календаря. Хотя вам может быть знаком григорианский календарь, используемый в большинстве западных стран, многие культуры используют совершенно другие системы календарей, такие как исламский календарь Хиджры, еврейский календарь или буддийский календарь.

Чтобы позволить пользователям выбирать предпочтительный календарь, необходимо знать, какие системы календарей поддерживаются в среде JavaScript. Вместо того чтобы поддерживать жестко закодированный список, который может устареть или включать неподдерживаемые значения, JavaScript предоставляет метод для определения доступных систем календарей во время выполнения.

Метод Intl.supportedValuesOf() с параметром "calendar" возвращает массив всех идентификаторов систем календарей, поддерживаемых текущей средой. Это гарантирует, что ваше приложение предлагает только те варианты календарей, которые работают корректно.

Что такое системы календарей

Системы календарей — это различные методы организации и отсчета времени. Хотя все календари отслеживают дни, месяцы и годы, они используют разные правила для определения начала года, продолжительности месяцев и учета астрономических циклов.

Григорианский календарь, введенный в 1582 году, является наиболее широко используемым гражданским календарем. Он основан на солнечном году продолжительностью примерно 365,25 дней, разделенном на 12 месяцев, с добавлением високосных лет каждые четыре года (с исключениями для вековых лет).

Другие системы календарей следуют иным правилам. Исламский календарь является чисто лунным, с месяцами, соответствующими фазам Луны. Это означает, что исламский год примерно на 11 дней короче солнечного года, из-за чего исламские даты со временем смещаются по сезонам. Еврейский и китайский календари являются лунно-солнечными, сочетая лунные месяцы с корректировками для синхронизации с солнечным годом.

При форматировании дат в JavaScript система календаря определяет, какие числа года, месяца и дня будут отображаться. Один и тот же момент времени может иметь разные представления даты в разных системах календарей.

Использование Intl.supportedValuesOf для получения систем календарей

Метод Intl.supportedValuesOf() принимает строковый параметр, указывающий, какие значения нужно вернуть. Чтобы получить системы календарей, передайте "calendar":

const calendars = Intl.supportedValuesOf("calendar");
console.log(calendars);
// Вывод: ["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 поддерживают разные наборы календарей, хотя все современные браузеры поддерживают основной набор общих календарей.

Понимание идентификаторов календарей

Каждый идентификатор календаря представляет определённую систему календаря, используемую одной или несколькими культурами. Вот наиболее распространённые идентификаторы:

Идентификатор "gregory" представляет григорианский календарь, стандартный гражданский календарь, используемый в большинстве стран. Это календарь, который вы используете в повседневной жизни, если живёте в США, Европе или большинстве других частей мира.

Идентификатор "buddhist" представляет тайский буддийский календарь, который использует ту же структуру месяцев и дней, что и григорианский календарь, но отсчитывает годы от рождения Будды (543 год до н.э. по григорианскому календарю). 2025 год по григорианскому календарю соответствует 2568 году по буддийскому календарю.

Идентификатор "chinese" представляет традиционный китайский календарь, лунно-солнечный календарь, где месяцы следуют фазам Луны, а годы согласуются с орбитальным периодом Юпитера. Китайский календарь используется для определения традиционных праздников, таких как Китайский Новый год.

Идентификатор "islamic" представляет исламский календарь Хиджры, чисто лунный календарь с 12 месяцами по 29 или 30 дней. Годы отсчитываются от Хиджры, когда Мухаммад переселился из Мекки в Медину в 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));
// Вывод: "October 15, 2025"

console.log(islamic.format(date));
// Вывод: "Rabi' II 16, 1447 AH"

console.log(hebrew.format(date));
// Вывод: "Tishrei 23, 5786"

console.log(buddhist.format(date));
// Вывод: "October 15, 2568 BE"

Один и тот же объект JavaScript Date представляет один и тот же момент времени, но каждая календарная система отображает этот момент с разными значениями года, месяца и дня. Григорианская дата 15 октября 2025 года соответствует 16 Раби' II 1447 года в исламском календаре, 23 Тишрея 5786 года в еврейском календаре и 15 октября 2568 года в буддийском календаре.

Это демонстрирует, почему необходимо указывать, какую календарную систему использовать при форматировании дат для пользователей, которые следуют разным календарям.

Создание селектора календаря

При создании пользовательских интерфейсов, которые позволяют пользователям выбирать предпочитаемый календарь, запросите доступные календари и динамически создайте селектор:

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)}`);
});

// Результат:
// 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("Исламский календарь не поддерживается");
}

Этот шаблон работает для любого идентификатора календаря. Вы можете использовать его, чтобы показывать или скрывать варианты календарей в пользовательском интерфейсе или переключаться на календарь по умолчанию, если предпочтительный недоступен.

Создание функции обнаружения возможностей для повторного использования

Создайте универсальную функцию для проверки поддержки календарей и аккуратной обработки отсутствующих календарей:

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);
// Вывод: ["gregory", "islamic", "hebrew", "buddhist"]
// (предполагая, что все они поддерживаются в текущей среде)

Это гарантирует, что вы предлагаете только те календари, которые соответствуют вашим требованиям и работают в браузере пользователя.

Вы можете объединить это с выбором календаря, чтобы создать целенаправленный пользовательский интерфейс:

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);
// Вывод:
// {
//   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() был добавлен в JavaScript в 2022 году. Старые браузеры его не поддерживают. Проверьте, существует ли метод, перед его использованием:

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"
  ];
}

Этот список предоставляет разумный набор календарей для старых браузеров, хотя нельзя гарантировать, что все они будут работать в каждой среде.

Понимание разницы между календарем и локалью

Календарь и локаль — это связанные, но отдельные понятия. Локаль определяет язык и региональные форматы, а календарь определяет, какая система календаря будет использоваться.

Одна локаль может использовать несколько календарей. Например, арабоязычные жители Саудовской Аравии обычно используют исламский календарь для религиозных целей и григорианский календарь для гражданских целей. Вы можете форматировать даты на арабском языке, используя любой из этих календарей:

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));
// Результат: "١٥ أكتوبر ٢٠٢٥"

console.log(arabicIslamic.format(date));
// Результат: "١٦ ربيع الآخر ١٤٤٧ هـ"

Оба используют арабский текст и числа, но показывают разные даты в соответствии с их календарями.

И наоборот, вы можете форматировать один и тот же календарь на разных языках:

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));
// Результат: "Rabi' II 16, 1447 AH"

console.log(arabicIslamic.format(date));
// Результат: "١٦ ربيع الآخر ١٤٤٧ هـ"

Оба показывают исламскую дату, но используют разные языки и системы чисел.

Когда запрашивать доступные календари

Запрашивайте доступные календари в следующих ситуациях:

При создании интерфейса выбора календаря вызывайте Intl.supportedValuesOf("calendar"), чтобы заполнить варианты. Это гарантирует, что вы показываете только те календари, которые работают в текущей среде.

При реализации функций, основанных на календаре, проверьте, поддерживается ли требуемый календарь, прежде чем его использовать. Это предотвращает ошибки и позволяет плавно переключаться на альтернативные календари.

При сохранении пользовательских предпочтений проверяйте выбор календаря на соответствие поддерживаемому списку. Это гарантирует, что сохраненные предпочтения останутся действительными на разных устройствах и в разных браузерах.

При миграции между средами проверьте поддержку календарей как в исходной, так и в целевой среде. Поддержка календарей может различаться между версиями браузеров, версиями Node.js и различными средами выполнения JavaScript.

При загрузке данных, зависящих от календаря, убедитесь, что календарь доступен, прежде чем пытаться анализировать или форматировать даты. Это предотвращает ошибки выполнения при работе с датами в определенных системах календарей.