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

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

Введение

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

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

Календарные системы определяют, как общества считают время. Они задают начало года, организацию месяцев и нумерацию дат. API Intl.DateTimeFormat в JavaScript поддерживает более 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));
// Вывод: "March 15, 2025"

console.log(islamicFormatter.format(date));
// Вывод: "Ramadan 16, 1447 AH"

Один и тот же объект Date создает разные отформатированные строки в зависимости от системы календаря. Григорианский календарь показывает 15 марта 2025 года. Исламский календарь показывает 16 Рамадана 1447 года AH.

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

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));
// Вывод: "١٦ رمضان ١٤٤٧ هـ"

console.log(englishFormatter.format(date));
// Вывод: "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));
// Вывод: "Рамадан 16, 1447 AH"

console.log(umalquraFormatter.format(date));
// Вывод: "Рамадан 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));
// Вывод: "14 Адар 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));
// Вывод: "י״ד באדר ב׳ ה׳תשפ״ה"

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

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

Японский календарь использует названия эпох, основанные на правящем императоре. Текущая эпоха, Рэйва, началась 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)"

Год китайского календаря включает циклическое название (в данном случае 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));
// Вывод: "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));
// Вывод: "۲۴ اسفند ۱۴۰۳ ه‍.ش."

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

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));
// Вывод: "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));
// Вывод: "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));
// Вывод: "Amshir 6, 1741 ERA1"

console.log(ethiopicFormatter.format(date));
// Вывод: "Yekatit 6, 2017 ERA1"

Обнаружение поддерживаемых календарей

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

const calendars = Intl.supportedValuesOf('calendar');
console.log(calendars);
// Вывод: ["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));
// Вывод: "Рамадан 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));
// Вывод: "14 Адар 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));
// Вывод: "March 15, 2025"

console.log(saFormatter.format(date));
// Вывод: "١٦ رمضان ١٤٤٧ هـ"

console.log(ilFormatter.format(date));
// Вывод: "15 במרץ 2025"

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

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

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

const options = formatter.resolvedOptions();
console.log(options.calendar);
// Вывод: "islamic-umalqura"

Результаты метода resolvedOptions показывают, что локаль 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));
// Вывод зависит от локали пользователя и календаря по умолчанию

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

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

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

console.log(formatter.format(date));
// Вывод показывает дату в исламском календаре на языке пользователя

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

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

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

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

console.log(formatter.format(date));
// Вывод: "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));
// Вывод: "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));
// Вывод: "Saturday, March 15, 7 Reiwa"

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

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

Метод 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));
// Вывод: "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));
});
// Вывод:
// "Ramadan 16, 1447 AH"
// "Dhuʻl-Qiʻdah 22, 1447 AH"
// "Dhuʻl-Hijjah 15, 1447 AH"

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

Что нужно помнить

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

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

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

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