Библиотека Js-joda построена вокруг стандарта ISO-8601, однако экосистема Java Time API изначально поддерживает и другие календарные системы. Концепция альтернативных календарей особенно важна при разработке международных приложений, систем документооборота, архивов, финансовых платформ и государственных сервисов, где дата может быть представлена не только в григорианском календаре.
В Js-joda основная библиотека ориентирована на ISO-календарь, но архитектура Temporal API и подходы Java Time позволяют моделировать альтернативные хронологии через:
Ключевая идея — отделение:
Один и тот же момент времени может быть отображён разными календарями.
Большинство разработчиков неявно предполагают, что:
2025-05-01
— универсальная дата. На практике это лишь представление даты в ISO-календаре.
В разных странах и исторических периодах используются:
При этом:
Основной тип локальной даты:
const { LocalDate } = require('@js-joda/core')
const date = LocalDate.of(2025, 5, 10)
LocalDate всегда использует ISO-8601.
Внутри:
Это означает, что Js-joda не предоставляет полноценные альтернативные
хронологии «из коробки», как Java SE (JapaneseDate,
HijrahDate, ThaiBuddhistDate), однако
библиотека позволяет строить подобную логику поверх существующей
модели.
Во многих календарях год отсчитывается не от рождения Христа.
Например:
| Календарь | Начало эпохи |
|---|---|
| Буддийский | 543 год до н.э. |
| Японский | Начало правления императора |
| Исламский | Хиджра |
| Еврейский | Сотворение мира |
Js-joda предоставляет тип Era.
const { IsoEra } = require('@js-joda/core')
console.log(IsoEra.CE)
console.log(IsoEra.BCE)
Результат:
CE
BCE
Эпохи особенно важны при:
const { LocalDate } = require('@js-joda/core')
const caesarDeath = LocalDate.of(-44, 3, 15)
console.log(caesarDeath.toString())
Результат:
-0044-03-15
Здесь используется астрономическая система нумерации:
| Исторический год | Js-joda |
|---|---|
| 1 до н.э. | 0 |
| 2 до н.э. | -1 |
| 44 до н.э. | -43 |
Это важно учитывать при интеграции с историческими источниками.
Js-joda использует пролептический ISO-календарь.
Это означает:
Например:
const date = LocalDate.of(1400, 10, 10)
Дата будет валидной даже несмотря на то, что в тот период использовался юлианский календарь.
Самый простой вариант — преобразовывать ISO-даты в альтернативное представление.
Буддийский календарь:
BE = CE + 543
Пример:
const { LocalDate } = require('@js-joda/core')
function toBuddhist(date) {
return {
year: date.year() + 543,
month: date.monthValue(),
day: date.dayOfMonth()
}
}
const date = LocalDate.of(2025, 5, 10)
console.log(toBuddhist(date))
Результат:
{
year: 2568,
month: 5,
day: 10
}
В японской системе годы отсчитываются по эпохам императоров.
| Эпоха | Начало |
|---|---|
| Reiwa | 2019 |
| Heisei | 1989 |
| Showa | 1926 |
const ERAS = [
{
name: 'Reiwa',
start: 2019
},
{
name: 'Heisei',
start: 1989
},
{
name: 'Showa',
start: 1926
}
]
function toJapanese(date) {
const year = date.year()
const era = ERAS.find(e => year >= e.start)
return {
era: era.name,
year: year - era.start + 1,
month: date.monthValue(),
day: date.dayOfMonth()
}
}
Использование:
const { LocalDate } = require('@js-joda/core')
const date = LocalDate.of(2025, 5, 10)
console.log(toJapanese(date))
Результат:
{
era: 'Reiwa',
year: 7,
month: 5,
day: 10
}
В лунных календарях:
ISO:
1 января
Исламский календарь:
Еврейский календарь:
Некоторые страны переходили с юлианского на григорианский календарь в разное время.
Например:
| Страна | Переход |
|---|---|
| Италия | 1582 |
| Россия | 1918 |
| Греция | 1923 |
Из-за этого одна и та же историческая дата может иметь разные представления.
Наиболее надёжный способ работы с альтернативными календарями — хранение даты через абсолютное количество дней.
Js-joda предоставляет:
toEpochDay()
const { LocalDate } = require('@js-joda/core')
const date = LocalDate.of(2025, 5, 10)
console.log(date.toEpochDay())
Результат:
20218
Это количество дней от:
1970-01-01
Абсолютный номер дня:
Следовательно:
ISO дата
↓
epoch day
↓
альтернативный календарь
— наиболее безопасная схема преобразования.
class CustomCalendarDate {
constructor(epochDay) {
this.epochDay = epochDay
}
static fromIso(date) {
return new CustomCalendarDate(
date.toEpochDay()
)
}
toIso() {
return LocalDate.ofEpochDay(
this.epochDay
)
}
}
Такой подход позволяет:
const { LocalDate } = require('@js-joda/core')
const date = LocalDate.ofEpochDay(0)
console.log(date.toString())
Результат:
1970-01-01
Полная реализация исламского календаря крайне сложна:
Поэтому обычно используется:
JavaScript Intl.DateTimeFormat поддерживает
альтернативные календари.
const formatter = new Intl.DateTimeFormat(
'ja-JP-u-ca-japanese',
{
year: 'numeric',
month: 'long',
day: 'numeric'
}
)
console.log(
formatter.format(new Date())
)
Возможный результат:
令和7年5月10日
Наиболее практичная архитектура:
Js-joda
↓
точные вычисления
↓
Date
↓
Intl
↓
локализованное представление
const { LocalDate } = require('@js-joda/core')
const date = LocalDate.of(2025, 5, 10)
const jsDate = new Date(
date.year(),
date.monthValue() - 1,
date.dayOfMonth()
)
const formatter = new Intl.DateTimeFormat(
'th-TH-u-ca-buddhist'
)
console.log(
formatter.format(jsDate)
)
const formatter = new Intl.DateTimeFormat(
'th-TH-u-ca-buddhist',
{
year: 'numeric',
month: 'long',
day: 'numeric'
}
)
| Код | Календарь |
|---|---|
| buddhist | Буддийский |
| chinese | Китайский |
| coptic | Коптский |
| ethiopic | Эфиопский |
| hebrew | Еврейский |
| indian | Индийский |
| islamic | Исламский |
| japanese | Японский |
| persian | Персидский |
console.log(
Intl.supportedValuesOf('calendar')
)
Дата включает:
Например:
'ja-JP-u-ca-japanese'
означает:
| Часть | Значение |
|---|---|
| ja | японский язык |
| JP | Япония |
| u-ca-japanese | японский календарь |
const formatter = new Intl.DateTimeFormat(
'he-IL-u-ca-hebrew',
{
dateStyle: 'full'
}
)
const formatter = new Intl.DateTimeFormat(
'ar-SA-u-ca-islamic',
{
dateStyle: 'full'
}
)
Ключевой архитектурный принцип:
| Задача | Инструмент |
|---|---|
| Арифметика дат | Js-joda |
| Хранение | ISO / epoch |
| Локализация | Intl |
| Альтернативные календари | Intl / адаптер |
ISO-календарь:
Альтернативные календари часто используются только как:
Плохо:
"令和7年5月10日"
Хорошо:
2025-05-10
Плохо:
2568 + 1
Хорошо:
ISO date + 1 year
↓
конвертация
Опасный код:
if (year === 2568)
Без понимания календарной системы это приводит к ошибкам бизнес-логики.
Используется:
LocalDate
Instant
ZonedDateTime
Используется:
ISO-8601
epoch milliseconds
epoch day
Используется:
Intl.DateTimeFormat
или собственный адаптер календаря.
class CalendarConverter {
static toBuddhist(date) {
return {
year: date.year() + 543,
month: date.monthValue(),
day: date.dayOfMonth()
}
}
static toJapanese(date) {
const eras = [
['Reiwa', 2019],
['Heisei', 1989]
]
for (const [name, start] of eras) {
if (date.year() >= start) {
return {
era: name,
year: date.year() - start + 1,
month: date.monthValue(),
day: date.dayOfMonth()
}
}
}
}
}
Преобразования календарей могут быть дорогими:
Поэтому:
Создание форматтера дорогостоящее.
Плохо:
function format(date) {
return new Intl.DateTimeFormat(
'ja-JP-u-ca-japanese'
).format(date)
}
Хорошо:
const formatter =
new Intl.DateTimeFormat(
'ja-JP-u-ca-japanese'
)
function format(date) {
return formatter.format(date)
}
Поддержка календарей зависит от:
Например:
console.log(
Intl.DateTimeFormat.supportedLocalesOf([
'th-TH-u-ca-buddhist'
])
)
Полная реализация нужна в:
Во многих остальных случаях достаточно: