Hijrah календарь

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

Класс HijrahDate входит в модуль хронологий (chrono) и предоставляет полноценную работу с исламскими датами: создание объектов, арифметику дат, преобразования, форматирование и межкалендарные операции.


Подключение модуля chrono

Поддержка альтернативных календарей находится в отдельном пакете:

npm install @js-joda/core
npm install @js-joda/locale
npm install @js-joda/extra

Импорт:

import {
    HijrahChronology,
    HijrahDate,
    ChronoField,
    ChronoUnit
} from '@js-joda/core'

Особенности исламского календаря

Hijrah-календарь обладает рядом особенностей:

Особенность Описание
Лунный календарь Основан на фазах Луны
12 месяцев Как и в ISO-календаре
Год короче Обычно 354 или 355 дней
Смещение относительно ISO Годы не совпадают
Названия месяцев Muharram, Safar, Ramadan и др.

Создание HijrahDate

Текущая дата

const date = HijrahDate.now()

console.log(date.toString())

Пример результата:

Hijrah-umalqura AH 1447-09-12

Создание через chronology

const chronology = HijrahChronology.INSTANCE

const date = chronology.date(1445, 9, 1)

console.log(date.toString())

Создание конкретной даты

const date = HijrahDate.of(1445, 10, 15)

console.log(date.toString())

Структура даты

Hijrah-дата состоит из:

  • года (year)
  • месяца (month)
  • дня месяца (dayOfMonth)
  • эры (era)

Пример:

const date = HijrahDate.of(1445, 9, 20)

console.log(date.year())
console.log(date.monthValue())
console.log(date.dayOfMonth())

Получение полей даты

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

const date = HijrahDate.of(1445, 9, 10)

console.log(date.get(ChronoField.YEAR))
console.log(date.get(ChronoField.MONTH_OF_YEAR))
console.log(date.get(ChronoField.DAY_OF_MONTH))

Получение дня года

console.log(
    date.get(ChronoField.DAY_OF_YEAR)
)

День недели

console.log(
    date.get(ChronoField.DAY_OF_WEEK)
)

Значения:

День Значение
Monday 1
Tuesday 2
Wednesday 3
Thursday 4
Friday 5
Saturday 6
Sunday 7

Преобразование из ISO-календаря

Из LocalDate в HijrahDate

import { LocalDate } from '@js-joda/core'

const isoDate = LocalDate.of(2025, 3, 10)

const hijrahDate =
    HijrahChronology.INSTANCE.date(isoDate)

console.log(hijrahDate.toString())

Обратное преобразование

const isoDate = LocalDate.from(hijrahDate)

console.log(isoDate.toString())

Арифметика дат

Добавление дней

const date = HijrahDate.of(1445, 9, 1)

const next = date.plusDays(10)

console.log(next.toString())

Вычитание дней

const prev = date.minusDays(5)

console.log(prev.toString())

Добавление месяцев

const result = date.plusMonths(2)

console.log(result.toString())

Добавление лет

const result = date.plusYears(1)

console.log(result.toString())

Универсальная арифметика через ChronoUnit

const date = HijrahDate.of(1445, 9, 10)

console.log(
    date.plus(2, ChronoUnit.MONTHS)
)

console.log(
    date.minus(15, ChronoUnit.DAYS)
)

Изменение отдельных полей

Изменение года

const updated = date.withYear(1450)

console.log(updated.toString())

Изменение месяца

const updated = date.withMonth(12)

console.log(updated.toString())

Изменение дня

const updated = date.withDayOfMonth(1)

console.log(updated.toString())

Работа с эпохами

Исламский календарь использует эру AH (Anno Hegirae).

console.log(date.era())

Длина месяца

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

console.log(date.lengthOfMonth())

Результат:

29

или

30

Длина года

console.log(date.lengthOfYear())

Возможные значения:

354
355

Проверка високосного года

console.log(date.isLeapYear())

Сравнение дат

equals

const d1 = HijrahDate.of(1445, 9, 1)
const d2 = HijrahDate.of(1445, 9, 1)

console.log(d1.equals(d2))

isBefore / isAfter

console.log(d1.isBefore(d2))
console.log(d1.isAfter(d2))

compareTo

console.log(
    d1.compareTo(d2)
)

Получение эпохального дня

Все хронологии в Js-joda могут быть представлены через epoch day.

console.log(date.toEpochDay())

Это позволяет сравнивать даты разных календарей.


Межкалендарные операции

Сравнение ISO и Hijrah дат

const iso = LocalDate.of(2025, 1, 1)

const hijrah =
    HijrahChronology.INSTANCE.date(iso)

console.log(hijrah.toEpochDay())
console.log(iso.toEpochDay())

Значения будут одинаковыми.


Temporal API

HijrahDate поддерживает интерфейсы temporal API.

Проверка поддержки поля

console.log(
    date.isSupported(ChronoField.YEAR)
)

Проверка поддержки unit

console.log(
    date.isSupported(ChronoUnit.MONTHS)
)

Форматирование

Стандартное строковое представление

console.log(date.toString())

Пример:

Hijrah-umalqura AH 1445-09-10

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

import {
    DateTimeFormatter
} from '@js-joda/core'

const formatter =
    DateTimeFormatter.ofPattern(
        'yyyy-MM-dd'
    )

console.log(
    formatter.format(date)
)

Форматирование с chronology

const formatter =
    DateTimeFormatter
        .ofPattern('dd MMM yyyy')
        .withChronology(
            HijrahChronology.INSTANCE
        )

console.log(formatter.format(date))

Парсинг строк

const formatter =
    DateTimeFormatter.ofPattern(
        'yyyy-MM-dd'
    )

const parsed =
    HijrahDate.from(
        formatter.parse('1445-09-15')
    )

console.log(parsed.toString())

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

Первый день месяца

const first =
    date.withDayOfMonth(1)

console.log(first.toString())

Последний день месяца

const last =
    date.withDayOfMonth(
        date.lengthOfMonth()
    )

console.log(last.toString())

Получение диапазона значений

console.log(
    date.range(
        ChronoField.DAY_OF_MONTH
    )
)

Chronology API

Получение chronology

console.log(
    date.chronology()
)

Идентификатор chronology

console.log(
    date.chronology().id()
)

Тип календаря

console.log(
    date.chronology().calendarType()
)

Работа с периодами

ChronoPeriod

const start =
    HijrahDate.of(1445, 1, 1)

const end =
    HijrahDate.of(1445, 10, 1)

const period =
    start.until(end)

console.log(period.toString())

Расчёт интервалов

Количество дней между датами

const days =
    ChronoUnit.DAYS.between(
        start,
        end
    )

console.log(days)

Количество месяцев

const months =
    ChronoUnit.MONTHS.between(
        start,
        end
    )

console.log(months)

Иммутабельность

Все объекты HijrahDate неизменяемы.

const original =
    HijrahDate.of(1445, 9, 1)

const changed =
    original.plusDays(5)

console.log(original.toString())
console.log(changed.toString())

Исходный объект не изменяется.


Ошибки и исключения

Некорректная дата

HijrahDate.of(1445, 15, 10)

Ошибка:

DateTimeException

Некорректный день месяца

HijrahDate.of(1445, 9, 35)

Поддерживаемые операции

Операция Поддержка
plusDays Да
plusMonths Да
plusYears Да
minusDays Да
compareTo Да
until Да
format Да
parse Да

Практический пример: определение Ramadan

const date = HijrahDate.now()

if (date.monthValue() === 9) {
    console.log('Ramadan')
}

Практический пример: вычисление конца месяца

function endOfMonth(date) {
    return date.withDayOfMonth(
        date.lengthOfMonth()
    )
}

const result =
    endOfMonth(
        HijrahDate.of(1445, 9, 1)
    )

console.log(result.toString())

Практический пример: конвертация ISO → Hijrah

import {
    LocalDate,
    HijrahChronology
} from '@js-joda/core'

const iso =
    LocalDate.of(2026, 1, 15)

const hijrah =
    HijrahChronology
        .INSTANCE
        .date(iso)

console.log(iso.toString())
console.log(hijrah.toString())

Практический пример: сортировка Hijrah дат

const dates = [
    HijrahDate.of(1445, 9, 10),
    HijrahDate.of(1445, 1, 5),
    HijrahDate.of(1445, 12, 1)
]

dates.sort((a, b) =>
    a.compareTo(b)
)

console.log(dates)

Ограничения Hijrah календаря

Непостоянная длина месяцев

Нельзя жёстко рассчитывать количество дней в месяце.

Неправильно:

date.plusDays(30)

Правильно:

date.plusMonths(1)

Несовпадение с ISO

Между календарями отсутствует прямое соответствие месяцев и годов.


Разные реализации исламских календарей

В мире существует несколько вариантов Hijrah-календарей:

  • Umm al-Qura
  • tabular
  • observational

Js-joda использует реализацию Hijrah-umalqura.


Рекомендации по использованию

Использование ChronoUnit вместо ручных вычислений

Предпочтительно:

date.plus(1, ChronoUnit.MONTHS)

Вместо:

date.plusDays(30)

Использование epoch day для межкалендарных сравнений

date1.toEpochDay() === date2.toEpochDay()

Проверка длины месяца

const maxDay =
    date.lengthOfMonth()

Взаимодействие с другими temporal-типами

LocalDate

const iso =
    LocalDate.from(hijrahDate)

ChronoLocalDate

const chronoDate = hijrahDate

TemporalAccessor

const year =
    hijrahDate.get(
        ChronoField.YEAR
    )

Внутренняя модель времени

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

  • корректное сравнение
  • эффективную арифметику
  • совместимость между календарями
  • точность преобразований

Поддержка сериализации

const json =
    JSON.stringify({
        date: date.toString()
    })

console.log(json)

Восстановление даты из JSON

const parsed =
    HijrahDate.from(
        DateTimeFormatter
            .ISO_LOCAL_DATE
            .parse('1445-09-01')
    )