Как форматировать даты в разных календарных системах?

Используйте опцию calendar в JavaScript, чтобы отображать даты по исламскому, еврейскому, японскому, китайскому и другим календарям для пользователей по всему миру

Введение

Большинство разработчиков считают, что даты работают одинаково везде. Вы создаёте объект Date для 15 марта 2025 года, форматируете его и показываете пользователям. Но это предположение не работает, если ваши пользователи используют разные календарные системы.

Во многих культурах дата 15 марта 2025 года по григорианскому календарю выглядит совершенно иначе. Для пользователей, следующих исламскому календарю, этот момент соответствует 16 Рамадана 1447 года. Для тех, кто использует еврейский календарь, это 14 Адара II 5785 года. Для японских пользователей в официальных контекстах это будет 7-й год Рэйва, 15 марта.

Календарные системы определяют, как общества считают время. Они задают, когда начинается год, как организованы месяцы и как нумеруются даты. API JavaScript Intl.DateTimeFormat поддерживает более 17 различных календарных систем, позволяя отображать даты с учётом культурного и религиозного контекста каждого пользователя.

В этом уроке объясняется, что такое календарные системы, зачем они нужны и как форматировать даты с использованием разных календарных систем в JavaScript.

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

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

Григорианский календарь, широко используемый в международном контексте, ведёт отсчёт лет от предполагаемого рождения Иисуса Христа. В нём 12 месяцев продолжительностью от 28 до 31 дня, что формирует 365-дневный год с високосными годами каждые четыре года.

Другие календарные системы имеют разные структуры и начальные точки отсчёта. В исламском календаре 12 лунных месяцев, всего 354 или 355 дней в году. Еврейский календарь сочетает лунные месяцы с выравниванием по солнечному году. В японском календаре используются названия эр, которые меняются с каждым новым императором.

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

Почему календарные системы важны для приложений

Приложения, рассчитанные на пользователей из разных культур, должны отображать даты в привычных и ожидаемых для них форматах. Приложение для молитвенных времён для мусульман должно показывать даты по исламскому календарю. Приложение для еврейских религиозных праздников — по еврейскому календарю. Для государственных сервисов Японии требуется форматирование дат по японским эрам.

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

Один и тот же объект Date представляет один и тот же момент времени вне зависимости от календарной системы. Меняется только то, как этот момент отображается. В JavaScript можно отформатировать любую дату в любой поддерживаемой календарной системе без сложных преобразований.

Использование опции calendar

Конструктор Intl.DateTimeFormat принимает опцию calendar, которая указывает, какую календарную систему использовать при форматировании дат. Передайте идентификатор календаря в виде строки.

const date = new Date('2025-03-15');

const gregorianFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'gregory',
  dateStyle: 'long'
});

const islamicFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic',
  dateStyle: 'long'
});

console.log(gregorianFormatter.format(date));
// Output: "March 15, 2025"

console.log(islamicFormatter.format(date));
// Output: "Ramadan 16, 1447 AH"

Один и тот же объект Date может давать разные отформатированные строки в зависимости от календарной системы. В григорианском календаре это 15 марта 2025 года. В исламском календаре — 16 Рамадана 1447 года хиджры.

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

const date = new Date('2025-03-15');

const arabicFormatter = new Intl.DateTimeFormat('ar-SA', {
  calendar: 'islamic',
  dateStyle: 'long'
});

const englishFormatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic',
  dateStyle: 'long'
});

console.log(arabicFormatter.format(date));
// Output: "١٦ رمضان ١٤٤٧ هـ"

console.log(englishFormatter.format(date));
// Output: "Ramadan 16, 1447 AH"

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

Форматирование дат по исламскому календарю

Исламский календарь — это лунный календарь, состоящий из 12 месяцев по 29 или 30 дней. Полный лунный год составляет примерно 354 дня, что на 10–11 дней короче солнечного григорианского года. Поэтому исламские даты со временем сдвигаются назад относительно григорианского календаря.

JavaScript поддерживает несколько вариантов исламского календаря. Идентификатор islamic использует алгоритмический расчет. Идентификатор islamic-umalqura — календарь Умм аль-Кура, применяемый в Саудовской Аравии. Идентификатор islamic-civil использует предсказуемый табличный расчет.

const date = new Date('2025-03-15');

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

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

console.log(islamicFormatter.format(date));
// Output: "Ramadan 16, 1447 AH"

console.log(umalquraFormatter.format(date));
// Output: "Ramadan 16, 1447 AH"

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

Форматирование дат по еврейскому календарю

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

const date = new Date('2025-03-15');

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

console.log(hebrewFormatter.format(date));
// Output: "14 Adar II 5785"

В еврейском календаре используются еврейские названия месяцев, такие как нисан, ияр, сиван и таммуз. В високосные годы добавляются адар I и адар II. Отсчёт лет ведётся с момента сотворения мира по еврейской традиции.

Вы можете форматировать еврейские даты на иврите для носителей языка.

const date = new Date('2025-03-15');

const hebrewFormatter = new Intl.DateTimeFormat('he-IL', {
  calendar: 'hebrew',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(hebrewFormatter.format(date));
// Output: "י״ד באדר ב׳ ה׳תשפ״ה"

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

Форматирование дат по японскому календарю

В японском календаре используются названия эр, основанные на правящем императоре. Текущая эра, Рэйва, началась 1 мая 2019 года, когда император Нарухито взошёл на трон. Годы отсчитываются с начала каждой эры.

const date = new Date('2025-03-15');

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

console.log(japaneseFormatter.format(date));
// Output: "March 15, 7 Reiwa"

Год указывается как 7-й год Рэйва, что означает седьмой год эры Рэйва. Такой формат используется в официальных японских документах, государственных формах и формальных контекстах.

Форматирование на японском языке выдаёт традиционный японский формат даты.

const date = new Date('2025-03-15');

const japaneseFormatter = new Intl.DateTimeFormat('ja-JP', {
  calendar: 'japanese',
  era: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(japaneseFormatter.format(date));
// Output: "令和7年3月15日"

В выводе отображаются название эры (令和), год (7年), месяц (3月) и день (15日) с использованием японских иероглифов.

Форматирование дат по китайскому календарю

Китайский календарь — это лунно-солнечный календарь, который используется для определения традиционных китайских праздников, таких как Китайский Новый год и Праздник середины осени. Календарь сочетает лунные месяцы с солнечными периодами.

const date = new Date('2025-03-15');

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

console.log(chineseFormatter.format(date));
// Output: "Second Month 16, 2025(yi-si)"

Год по китайскому календарю включает циклическое название (например, и-сы) вместе с числовым годом. Названия месяцев обозначаются порядковыми номерами, например, «первый месяц», «второй месяц».

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

const date = new Date('2025-03-15');

const chineseFormatter = new Intl.DateTimeFormat('zh-CN', {
  calendar: 'chinese',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(chineseFormatter.format(date));
// Output: "2025乙巳年二月十六"

Форматирование дат по персидскому календарю

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

const date = new Date('2025-03-15');

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

console.log(persianFormatter.format(date));
// Output: "Esfand 24, 1403 AP"

1403 год по персидскому календарю соответствует 2025 году по григорианскому. Аббревиатура AP означает Anno Persico.

В персидской языковой локализации используются персидские цифры и названия месяцев.

const date = new Date('2025-03-15');

const persianFormatter = new Intl.DateTimeFormat('fa-IR', {
  calendar: 'persian',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(persianFormatter.format(date));
// Output: "۲۴ اسفند ۱۴۰۳ ه‍.ش."

Другие поддерживаемые календарные системы

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

Календарь buddhist добавляет 543 года к григорианскому году, используется в Таиланде и других странах с тхеравадинским буддизмом.

const date = new Date('2025-03-15');

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

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

Календарь indian является официальным гражданским календарём Индии.

const date = new Date('2025-03-15');

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

console.log(indianFormatter.format(date));
// Output: "Phalguna 24, 1946 Saka"

Календарь coptic используется Коптской православной церковью. Календарь ethiopic применяется в Эфиопии.

const date = new Date('2025-03-15');

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

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

console.log(copticFormatter.format(date));
// Output: "Amshir 6, 1741 ERA1"

console.log(ethiopicFormatter.format(date));
// Output: "Yekatit 6, 2017 ERA1"

Как узнать поддерживаемые календари

Метод Intl.supportedValuesOf() возвращает массив всех идентификаторов календарей, поддерживаемых в реализации JavaScript.

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

Точный список зависит от движка JavaScript и версии браузера. Метод всегда возвращает календари в алфавитном порядке.

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

const calendars = Intl.supportedValuesOf('calendar');
const supportsIslamic = calendars.includes('islamic');

if (supportsIslamic) {
  const formatter = new Intl.DateTimeFormat('en-US', {
    calendar: 'islamic',
    dateStyle: 'long'
  });
  console.log(formatter.format(new Date()));
}

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

Указание календаря через расширение Unicode

Можно указать календарную систему с помощью ключа расширения Unicode в идентификаторе локали вместо параметра options. Добавьте -u-ca- и идентификатор календаря к строке локали.

const date = new Date('2025-03-15');

const formatter = new Intl.DateTimeFormat('en-US-u-ca-islamic', {
  dateStyle: 'long'
});

console.log(formatter.format(date));
// Output: "Ramadan 16, 1447 AH"

Расширение -u-ca-islamic указывает форматтеру использовать исламский календарь. Это даёт тот же результат, что и передача calendar: 'islamic' в объекте options.

Если вы указываете календарь и в строке локали, и в объекте options, приоритет имеет объект options.

const date = new Date('2025-03-15');

const formatter = new Intl.DateTimeFormat('en-US-u-ca-islamic', {
  calendar: 'hebrew',
  dateStyle: 'long'
});

console.log(formatter.format(date));
// Output: "14 Adar II 5785"

Еврейский календарь из объекта options переопределяет исламский календарь из строки локали. Используйте параметр options, если нужно программно управлять календарной системой. Используйте Unicode-расширение, если работаете с идентификаторами локалей из пользовательских настроек или конфигурации.

Как локали определяют календарь по умолчанию

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

const date = new Date('2025-03-15');

const usFormatter = new Intl.DateTimeFormat('en-US', {
  dateStyle: 'long'
});

const saFormatter = new Intl.DateTimeFormat('ar-SA', {
  dateStyle: 'long'
});

const ilFormatter = new Intl.DateTimeFormat('he-IL', {
  dateStyle: 'long'
});

console.log(usFormatter.format(date));
// Output: "March 15, 2025"

console.log(saFormatter.format(date));
// Output: "١٦ رمضان ١٤٤٧ هـ"

console.log(ilFormatter.format(date));
// Output: "15 במרץ 2025"

В локали американского английского по умолчанию используется григорианский календарь. В локали арабского языка Саудовской Аравии по умолчанию используется исламский календарь. В локали иврита Израиля по умолчанию используется григорианский календарь, несмотря на сильные традиции еврейского календаря.

Вы можете узнать, какой календарь использует форматтер, вызвав метод resolvedOptions().

const formatter = new Intl.DateTimeFormat('ar-SA', {
  dateStyle: 'long'
});

const options = formatter.resolvedOptions();
console.log(options.calendar);
// Output: "islamic-umalqura"

В результатах resolved options видно, что локаль ar-SA по умолчанию использует вариант календаря islamic-umalqura.

Когда явно указывать календарь

Позвольте локали определять календарную систему при форматировании дат для общего отображения пользователям в их родной локали. Пользователи из Саудовской Аравии ожидают даты по исламскому календарю. Пользователи из Японии ожидают форматирование по японской эре в официальных контекстах. Значения по умолчанию для локалей автоматически учитывают эти ожидания.

const date = new Date('2025-03-15');

const formatter = new Intl.DateTimeFormat(navigator.language, {
  dateStyle: 'long'
});

console.log(formatter.format(date));
// Output varies by user's locale and default calendar

Явно указывайте календарь, если нужно отображать даты в определённой календарной системе независимо от локали пользователя. Например, в приложении для молитв всегда должны показываться даты по исламскому календарю. В приложении с еврейским календарём — всегда еврейские даты.

const date = new Date('2025-03-15');

const formatter = new Intl.DateTimeFormat(navigator.language, {
  calendar: 'islamic',
  dateStyle: 'long'
});

console.log(formatter.format(date));
// Output shows Islamic calendar date in user's language

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

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

const date = new Date('2025-03-15');

const formatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'hebrew',
  dateStyle: 'long'
});

console.log(formatter.format(date));
// Output: "14 Adar II 5785"

Комбинирование календаря с другими опциями

Опция календаря работает со всеми другими Intl.DateTimeFormat опциями. Её можно комбинировать с dateStyle, timeStyle, опциями компонентов, такими как weekday и month, а также с другими опциями, например timeZone.

const date = new Date('2025-03-15T14:30:00');

const formatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic',
  dateStyle: 'full',
  timeStyle: 'long',
  timeZone: 'America/New_York'
});

console.log(formatter.format(date));
// Output: "Saturday, Ramadan 16, 1447 AH at 2:30:00 PM EST"

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

Также можно использовать опцию календаря с отдельными опциями компонентов.

const date = new Date('2025-03-15');

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

console.log(formatter.format(date));
// Output: "Saturday, March 15, 7 Reiwa"

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

Форматирование диапазонов дат с разными календарями

Метод formatRange() работает с опцией календаря и позволяет форматировать диапазоны дат в любой календарной системе.

const startDate = new Date('2025-03-15');
const endDate = new Date('2025-03-25');

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

console.log(formatter.formatRange(startDate, endDate));
// Output: "Ramadan 16 – 26, 1447 AH"

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

Повторное использование форматтеров для производительности

Создание экземпляров Intl.DateTimeFormat включает обработку данных локали и информации о календарной системе. Если нужно отформатировать несколько дат с одним календарём и локалью, создайте форматтер один раз и используйте его повторно.

const formatter = new Intl.DateTimeFormat('en-US', {
  calendar: 'islamic',
  dateStyle: 'long'
});

const dates = [
  new Date('2025-03-15'),
  new Date('2025-04-20'),
  new Date('2025-06-10')
];

dates.forEach(date => {
  console.log(formatter.format(date));
});
// Output:
// "Ramadan 16, 1447 AH"
// "Dhuʻl-Qiʻdah 22, 1447 AH"
// "Dhuʻl-Hijjah 15, 1447 AH"

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

Что важно помнить

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

Укажите календарную систему с помощью опции calendar в объекте опций или добавив расширение Unicode к строке локали. Опция calendar определяет, какая календарная система будет использоваться для форматирования даты, а локаль — язык и правила форматирования.

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

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