Перейти к содержимому
v3.0.0
English
Русский
Документация

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

@vega-ui/i18n — независимый от фреймворка слой локалей и календарных систем, на котором работают Calendar, DayPicker, MonthPicker, YearPicker, DateField, DateTimeField и DateRangeField.

Значения дат

Каждая дата, пересекающая границу компонента, — это ISO-строка YYYY-MM-DD: value, defaultValue, min, max, month, year, disabledDates и ключи ячеек пикеров. Значения даты и времени имеют вид YYYY-MM-DDTHH:mm[:ss].

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

  • Ячейки DayPicker имеют ключом сам день.
  • Ячейки MonthPicker имеют ключом первый день каждого месяца.
  • Ячейки YearPicker имеют ключом первый день каждого года.
  • Пропсы month и year принимают любой день внутри этого месяца или года.

Установка

@vega-ui/react зависит от @vega-ui/i18n. Установите его напрямую, если приложение импортирует календарные адаптеры или помощники, — той же версии, что использует @vega-ui/react:

pnpm add @vega-ui/i18n

У @vega-ui/i18n нет зависимостей, и он ничего не знает о компонентах: в нём тип CalendarSystem, григорианский календарь, помощники для ISO-дней и календарно-зависимых дат, помощники для локалей и форматирования через Intl. Ввод дат с клавиатуры в полях дат построен поверх него в @vega-ui/utils.

Пропсы локали и календаря

Локаль и календарная система — корневые пропсы, как опции Intl.DateTimeFormat. Calendar, DateField, DateTimeField и DateRangeField, а также низкоуровневые DayPicker, MonthPicker и YearPicker принимают:

  • locale?: string: локаль BCP 47 для названий месяцев и дней и для первого дня недели. По умолчанию — локаль среды выполнения, getDefaultLocale().
  • calendar?: CalendarSystem: календарная система, в которой показываются месяцы и годы. По умолчанию — gregorianCalendar.

Каждая часть внутри корня (подписи месяца и года, сетка дней и её первый день недели, сетки месяцев и лет) берёт их из корня. Раскладки низкоуровневых пикеров также принимают собственный locale, чтобы переопределить его для этой раскладки. Calendar задаёт lang на своём корне, только если передан locale.

import { Calendar, CalendarMonthLabel } from '@vega-ui/react';

<Calendar locale='de-DE' defaultValue='2026-03-15'>
  <CalendarMonthLabel />
</Calendar>

getDefaultLocale() возвращает локаль среды выполнения, new Intl.DateTimeFormat().resolvedOptions().locale. При рендеринге на сервере передавайте locale явно, чтобы вывод сервера и клиента совпадал.

Остальные помощники для локалей принимают аргумент locales из Intl (тег, Intl.Locale или список) и, как Intl, используют локаль среды выполнения, если он не передан:

ПомощникРезультат
getFirstDayOfWeek(locale)Первый день недели в локали, 0 — воскресенье.
getWeekDayNames(locale, style, firstDayOfWeek?)Названия дней недели, начиная с первого дня недели.
getDateFormat(calendar, locale) / getDateSeparator(calendar, locale)Порядок числовых полей даты (DD.MM.YYYY) и их разделитель в локали и календарной системе. Эры и метки направления текста (bidi) опускаются; календарь с названиями месяцев (еврейский, китайский) использует григорианский порядок локали.
toLatinDigits(value)Заменяет цифры других систем счисления на латинские: ۱۴۰۵/۰۱/۰۱ → 1405/01/01.

Направление текста

Направление не выводится из locale. Как и нативные элементы, Calendar, пикеры и календари полей дат следуют направлению текста, в котором они отрендерены: dir на <html> или предке, атрибут dir на корне компонента или CSS-свойство direction.

import { Calendar, CalendarDays, CalendarWeekdays, CalendarWeekday, CalendarWeeks, CalendarDay } from '@vega-ui/react';

<div dir='rtl'>
  <Calendar locale='ar-EG'>
    <CalendarDays>
      <CalendarWeekdays>
        <CalendarWeekday />
      </CalendarWeekdays>
      <CalendarWeeks>
        <CalendarDay />
      </CalendarWeeks>
    </CalendarDays>
  </Calendar>
</div>

В контексте справа налево:

  • сетки дней, месяцев и лет идут справа налево, поэтому первый день недели — крайний правый столбец
  • ArrowRight переходит к предыдущей ячейке, а ArrowLeft — к следующей; ArrowUp, ArrowDown, Home и End не меняются
  • следующий месяц или страница лет находится слева: её показывают свайп влево и CalendarNextButton
  • стандартные шевроны CalendarPrevButton и CalendarNextButton отражены зеркально

Календарь, отрендеренный в портале, например в поповере, наследует направление контейнера портала, поэтому задавайте dir на <html> или на самом календаре.

Настройки на всё приложение

У библиотеки нет глобальных провайдеров. Чтобы везде использовать одну локаль или календарную систему, один раз оберните компонент в приложении и используйте обёртку:

import { Calendar, CalendarProps, CalendarSelection } from '@vega-ui/react';

const appLocale = 'de-DE';

export const AppCalendar = <S extends CalendarSelection = 'single'>(props: CalendarProps<S>) => (
  <Calendar locale={appLocale} {...props} />
);

Пропсы, переданные обёртке, по-прежнему имеют приоритет, поэтому отдельный экран может переопределить настройку приложения.

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

Календарная система — небольшой объект-адаптер. В пакете есть два вида, оба без зависимостей:

  • gregorianCalendar — общий экземпляр класса GregorianCalendar, используется, когда calendar не передан. Его методы привязаны к экземпляру, поэтому их можно передавать как колбэки, а класс можно расширять (см. Расширение GregorianCalendar);
  • createCalendarSystem(identifier) — любой другой календарь, встроенный в Intl: 'persian', 'hebrew', 'japanese', 'buddhist', 'roc', 'indian', 'coptic', 'ethiopic', 'islamic-umalqura', 'islamic-civil', 'chinese', 'dangi'… Идентификатор, который среда выполнения не поддерживает, выбрасывает RangeError.
import { createCalendarSystem } from '@vega-ui/i18n';

export const persianCalendar = createCalendarSystem('persian');

createCalendarSystem читает календарь из Intl.DateTimeFormat, поэтому результат совпадает с датами, которые форматирует Intl:

  • месяцы нумеруются от начала года: от 1 до 13 в високосный год еврейского календаря (включая Адар I и Адар II), а високосный месяц китайского календаря получает собственный номер;
  • эры — короткие английские названия эр из Intl, например 'Reiwa' и 'Heisei'; поля без эры используют текущую, поэтому введённый год — это год текущей эры;
  • год, который заканчивается вместе с эрой (японский Хэйсэй 31, с 1 января по 30 апреля 2019 года), содержит только свои месяцы; поля за его концом ограничиваются им;
  • результаты кешируются для каждого экземпляра, поэтому создавайте каждый календарь один раз.

Подойдёт и любой другой объект, реализующий интерфейс CalendarSystem:

ЧленНазначение
identifierИдентификатор календаря, понятный Intl.DateTimeFormat, например gregory, persian, hebrew.
toFields(date)Преобразует ISO-день в { era?, year, month, day } этого календаря; month и day начинаются с 1.
fromFields({ era?, year, month, day })Преобразует поля этого календаря обратно в ISO-день.
getMonthsInYear({ era?, year })Число месяцев в году, например 13 в високосный год еврейского календаря.
getDaysInMonth({ era?, year, month })Число дней в месяце.

Названия месяцев, лет и эр всегда берутся из Intl.DateTimeFormat с identifier, поэтому адаптер не занимается подписями.

Создавайте каждый календарь один раз, вне рендера, и передавайте его в корень компонента:

const persianCalendar = createCalendarSystem('persian');

<DateField format='yyyy/MM/dd' separator='/' locale='fa-IR' calendar={persianCalendar}>
  <DateFieldInput />
</DateField>

Ввод 1405/01/01 проверяется по длинам месяцев персидского календаря и превращается в ISO-день 2026-03-21.

Расширение GregorianCalendar

Календарь с теми же месяцами и днями, что у григорианского, может расширить GregorianCalendar вместо написания полного адаптера. Методы — обычные методы класса, привязанные в конструкторе, поэтому переопределения могут вызывать super и всё равно передаваться как колбэки.

Тайский солнечный буддийский календарь (buddhist в Intl) — это григорианский календарь, в котором годы отсчитываются от 543 года до н. э.:

import { GregorianCalendar, type CalendarDateFields } from '@vega-ui/i18n';

class BuddhistCalendar extends GregorianCalendar {
  readonly identifier = 'buddhist';

  toFields(date: string): CalendarDateFields {
    const fields = super.toFields(date);
    return { ...fields, year: fields.year + 543 };
  }

  fromFields(fields: CalendarDateFields) {
    return super.fromFields({ ...fields, year: fields.year - 543 });
  }

  getDaysInMonth(fields: Pick<CalendarDateFields, 'era' | 'year' | 'month'>) {
    return super.getDaysInMonth({ ...fields, year: fields.year - 543 });
  }
}

export const buddhistCalendar = new BuddhistCalendar();

buddhistCalendar.toFields('2026-02-15') даёт { year: 2569, month: 2, day: 15 }, а подписи берутся из Intl с идентификатором buddhist (2569 BE). Календари с собственными длинами месяцев, например персидский или еврейский, создаются через createCalendarSystem.

Помощники для дат

Все помощники принимают календарь первым аргументом, а принимают и возвращают ISO-строки YYYY-MM-DD.

ПомощникРезультат
getCalendarMonthStart(calendar, date) / getCalendarMonthEnd(calendar, date)Первый / последний день месяца, содержащего date.
getCalendarYearStart(calendar, date) / getCalendarYearEnd(calendar, date)Первый / последний день года, содержащего date.
addCalendarMonths(calendar, date, amount)Первый день месяца, отстоящего на amount месяцев.
addCalendarYears(calendar, date, amount)Первый день года, отстоящего на amount лет.
getCalendarMonthsDistance(calendar, from, to) / getCalendarYearsDistance(calendar, from, to)Число месяцев / лет между двумя днями со знаком.
getCalendarMonthsOfYear(calendar, date)Первые дни всех месяцев года.
getSameDayInCalendarMonth(calendar, date, month)Тот же номер дня в другом месяце, ограниченный его длиной.
getSameMonthInCalendarYear(calendar, month, year)Тот же номер месяца в другом году, ограниченный числом месяцев.
isSameCalendarMonth(calendar, a, b)Относятся ли два дня к одному месяцу.
addISODays(date, days) / getISOWeekday(date)Арифметика дней и день недели (0 — воскресенье), одинаковые в любом календаре.
DAYS_IN_WEEK7: все календари Intl используют ISO-неделю, которая продолжается через границы месяцев, лет и эр, поэтому в сетках дней всегда семь столбцов.

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

formatCalendarDate(calendar, date, locale, options) форматирует ISO-день через Intl.DateTimeFormat в календарной системе:

import { formatCalendarDate, gregorianCalendar } from '@vega-ui/i18n';

formatCalendarDate(gregorianCalendar, '2026-03-01', 'en-US', { month: 'short' });

formatCalendarDay(calendar, date, locale, format) форматирует только номер дня, без суффикса, который некоторые локали добавляют к отдельному дню: 15, а не 15日 в ja-JP.

Самое затратное — создание Intl.DateTimeFormat, поэтому для форматирования многих дней создайте один форматтер и переиспользуйте его. createCalendarDateFormatter(calendar, locale, options) и createCalendarDayFormatter(calendar, locale, format) возвращают (date) => string; их используют сетки дней, месяцев и лет:

import { createCalendarDayFormatter, gregorianCalendar } from '@vega-ui/i18n';

const formatDay = createCalendarDayFormatter(gregorianCalendar, 'ja-JP', 'numeric');
formatDay('2026-03-15'); // '15'

Помощники для шаблонов реализуют ввод с клавиатуры в полях дат, поэтому лежат в @vega-ui/utils и принимают календарную систему первым аргументом. Они читают цифры любой системы счисления (٠٥.٠١.٢٠٢٦), и поля дат принимают их при вводе. В шаблоне dd, mm и yyyy — день, месяц и год календаря, а HH, MM, SS — часы, минуты и секунды:

  • formatTemplateDate(calendar, date, template): ISO-значение в введённую строку
  • parseTemplateDate(calendar, value, template): введённая строка в ISO-значение или undefined, если она неполная или такой даты нет
  • parseTemplateRange(calendar, value, template, separator): введённый диапазон в кортеж [start, end], '' для неполной границы
  • normalizeTemplateDate(calendar, value, template, { min, max }): дополняет одиночную цифру, с которой не может начинаться двузначный сегмент, ограничивает заполненные сегменты рамками календаря и ограничивает полное значение пределами min / max
  • stepTimeSegment(value, template, position, direction, step): меняет сегмент времени под кареткой на шаг; время не зависит от календаря, поэтому календарь не передаётся

Частые ошибки

  • Передача объектов Date, временных меток, числовых годов или месяцев с нуля вместо ISO-строк.
  • Преобразование значений в отображаемый календарь перед сохранением; храните ISO-день, а форматирование оставьте календарю.
  • Создание нового адаптера на каждом рендере.
  • Разные значения calendar у пикера и окружающего его поля.