Значения дат
Каждая дата, пересекающая границу компонента, — это 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_WEEK | 7: все календари 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/maxstepTimeSegment(value, template, position, direction, step): меняет сегмент времени под кареткой на шаг; время не зависит от календаря, поэтому календарь не передаётся
Частые ошибки
- Передача объектов
Date, временных меток, числовых годов или месяцев с нуля вместо ISO-строк. - Преобразование значений в отображаемый календарь перед сохранением; храните ISO-день, а форматирование оставьте календарю.
- Создание нового адаптера на каждом рендере.
- Разные значения
calendarу пикера и окружающего его поля.