Date Values
Every date that crosses a component boundary is an ISO YYYY-MM-DD string: value, defaultValue, min, max, month, year, disabledDates, and the cell keys of the pickers. Date-time values use YYYY-MM-DDTHH:mm[:ss].
An ISO day is calendar-neutral. The calendar system only decides how days are grouped into months and years and how they are labeled, so the stored value never changes when the calendar does.
DayPickercells are keyed by the day itself.MonthPickercells are keyed by the first day of each month.YearPickercells are keyed by the first day of each year.monthandyearprops accept any day inside that month or year.
Install
@vega-ui/react depends on @vega-ui/i18n. Install it directly when the app imports the calendar adapters or helpers, keeping the same version as the one @vega-ui/react uses:
pnpm add @vega-ui/i18n@vega-ui/i18n has no dependencies and knows nothing about the components: it holds the CalendarSystem type, the Gregorian calendar, ISO day and calendar-aware date helpers, locale and Intl formatting helpers. Typed entry of the date fields builds on it in @vega-ui/utils.
Locale And Calendar Props
Locale and calendar system are root props, like the options of Intl.DateTimeFormat. Calendar, DateField, DateTimeField and DateRangeField, and the low-level DayPicker, MonthPicker and YearPicker, take:
locale?: string: BCP 47 locale of month and day names and of the first day of the week. Defaults to the locale of the runtime,getDefaultLocale().calendar?: CalendarSystem: calendar system months and years are shown in. Defaults togregorianCalendar.
Every part inside a root (month and year labels, the day grid and its first day of the week, the month and year grids) reads them from the root. The low-level picker layouts also accept their own locale to override it for that layout. Calendar sets lang on its root only when locale is passed.
import { Calendar, CalendarMonthLabel } from '@vega-ui/react';
<Calendar locale='de-DE' defaultValue='2026-03-15'>
<CalendarMonthLabel />
</Calendar>getDefaultLocale() returns the runtime locale, new Intl.DateTimeFormat().resolvedOptions().locale. Pass locale explicitly when rendering on the server, so server and client output match.
Other locale helpers take the locales argument of Intl (a tag, an Intl.Locale or a list) and, like Intl, use the runtime locale when it is omitted:
| Helper | Result |
|---|---|
getFirstDayOfWeek(locale) | First day of the week of a locale, 0 = Sunday. |
getWeekDayNames(locale, style, firstDayOfWeek?) | Weekday names starting from the first day of the week. |
getDateFormat(calendar, locale) / getDateSeparator(calendar, locale) | Order of the numeric date fields (DD.MM.YYYY) and their separator in a locale and calendar system. Eras and bidi marks are left out; a calendar that names its months (Hebrew, Chinese) uses the Gregorian order of the locale. |
toLatinDigits(value) | Replaces the digits of other numbering systems with Latin digits: ۱۴۰۵/۰۱/۰۱ → 1405/01/01. |
Text Direction
Direction is not derived from locale. Like native elements, Calendar, the pickers, and the calendars of the date fields follow the text direction they are rendered in: dir on <html> or an ancestor, a dir attribute on the component root, or the CSS direction property.
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>In a right-to-left context:
- day, month, and year grids run from right to left, so the first day of the week is the rightmost column
ArrowRightmoves to the previous cell andArrowLeftto the next one;ArrowUp,ArrowDown,Home, andEndare unchanged- the next month or page of years is on the left: swiping to the left and
CalendarNextButtonshow it - the default chevrons of
CalendarPrevButtonandCalendarNextButtonare mirrored
A calendar rendered in a portal, such as a popover, inherits the direction of the portal container, so set dir on <html> or on the calendar itself.
App-Wide Settings
The library has no global providers. To use the same locale or calendar system everywhere, wrap the component once in the app and use the wrapper instead:
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} />
);Props given to the wrapper still win, so a single screen can override the app setting.
Calendar System Adapter
A calendar system is a small adapter object. Two kinds ship with the package, neither with dependencies:
gregorianCalendar, the shared instance of theGregorianCalendarclass, used when nocalendaris given. Its methods are bound to the instance, so they can be passed as callbacks, and the class can be extended (see Extending GregorianCalendar);createCalendarSystem(identifier), any other calendar built intoIntl:'persian','hebrew','japanese','buddhist','roc','indian','coptic','ethiopic','islamic-umalqura','islamic-civil','chinese','dangi'… An identifier the runtime does not support throws aRangeError.
import { createCalendarSystem } from '@vega-ui/i18n';
export const persianCalendar = createCalendarSystem('persian');createCalendarSystem reads the calendar from Intl.DateTimeFormat, so the result is the same as the dates Intl formats:
- months are numbered from the start of the year: 1 to 13 in a Hebrew leap year (Adar I and Adar II included), and a leap month of the Chinese calendar gets its own number;
- eras are the short English era names of
Intl, such as'Reiwa'and'Heisei'; fields without an era use the current one, so a typed year is a year of the current era; - a year that ends with an era (Japanese Heisei 31, January 1 to April 30, 2019) has only its own months; fields past its end are clamped to it;
- results are cached per instance, so create each calendar once.
Any other object implementing the CalendarSystem interface works as well:
| Member | Purpose |
|---|---|
identifier | Calendar id understood by Intl.DateTimeFormat, such as gregory, persian, hebrew. |
toFields(date) | Converts an ISO day into { era?, year, month, day } of this calendar; month and day are 1-based. |
fromFields({ era?, year, month, day }) | Converts fields of this calendar back into an ISO day. |
getMonthsInYear({ era?, year }) | Number of months in a year, for example 13 in a Hebrew leap year. |
getDaysInMonth({ era?, year, month }) | Number of days in a month. |
Month, year, and era names always come from Intl.DateTimeFormat with identifier, so the adapter does not deal with labels.
Create each calendar once, outside of render, and pass it to the root of a component:
const persianCalendar = createCalendarSystem('persian');
<DateField format='yyyy/MM/dd' separator='/' locale='fa-IR' calendar={persianCalendar}>
<DateFieldInput />
</DateField>Typing 1405/01/01 is validated against Persian month lengths and resolves to the ISO day 2026-03-21.
Extending GregorianCalendar
A calendar that shares Gregorian months and days can extend GregorianCalendar instead of writing a full adapter. Methods are regular class methods bound in the constructor, so overrides can call super and still be passed as callbacks.
The Thai solar Buddhist calendar (buddhist in Intl) is the Gregorian calendar with years counted from 543 BC:
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') gives { year: 2569, month: 2, day: 15 }, and labels come from Intl with the buddhist identifier (2569 BE). Calendars with their own month lengths, such as Persian or Hebrew, come from createCalendarSystem.
Date Helpers
All helpers take the calendar first and take and return ISO YYYY-MM-DD strings.
| Helper | Result |
|---|---|
getCalendarMonthStart(calendar, date) / getCalendarMonthEnd(calendar, date) | First / last day of the month containing date. |
getCalendarYearStart(calendar, date) / getCalendarYearEnd(calendar, date) | First / last day of the year containing date. |
addCalendarMonths(calendar, date, amount) | First day of the month amount months away. |
addCalendarYears(calendar, date, amount) | First day of the year amount years away. |
getCalendarMonthsDistance(calendar, from, to) / getCalendarYearsDistance(calendar, from, to) | Signed number of months / years between two days. |
getCalendarMonthsOfYear(calendar, date) | First days of all months of the year. |
getSameDayInCalendarMonth(calendar, date, month) | Same day number in another month, clamped to its length. |
getSameMonthInCalendarYear(calendar, month, year) | Same month number in another year, clamped to its month count. |
isSameCalendarMonth(calendar, a, b) | Whether two days share a month. |
addISODays(date, days) / getISOWeekday(date) | Day arithmetic and weekday (0 = Sunday), the same in every calendar. |
DAYS_IN_WEEK | 7: every calendar of Intl uses the ISO week, which runs on across months, years and eras, so day grids always have seven columns. |
Formatting And Parsing
formatCalendarDate(calendar, date, locale, options) formats an ISO day with Intl.DateTimeFormat in a calendar system:
import { formatCalendarDate, gregorianCalendar } from '@vega-ui/i18n';
formatCalendarDate(gregorianCalendar, '2026-03-01', 'en-US', { month: 'short' });formatCalendarDay(calendar, date, locale, format) formats only the day number, without the suffix some locales add to a standalone day: 15 rather than 15日 in ja-JP.
Creating an Intl.DateTimeFormat is the costly part, so to format many days create one formatter and reuse it. createCalendarDateFormatter(calendar, locale, options) and createCalendarDayFormatter(calendar, locale, format) return (date) => string; the day, month and year grids use them:
import { createCalendarDayFormatter, gregorianCalendar } from '@vega-ui/i18n';
const formatDay = createCalendarDayFormatter(gregorianCalendar, 'ja-JP', 'numeric');
formatDay('2026-03-15'); // '15'Template helpers implement typed entry in the date fields, so they live in @vega-ui/utils and take the calendar system as their first argument. They read the digits of any numbering system (٠٥.٠١.٢٠٢٦), and the date fields accept them while typing. In a template, dd, mm, and yyyy are calendar day, month, and year, and HH, MM, SS are hours, minutes, and seconds:
formatTemplateDate(calendar, date, template): ISO value to a typed stringparseTemplateDate(calendar, value, template): typed string to ISO value, orundefinedwhen incomplete or non-existentparseTemplateRange(calendar, value, template, separator): typed range to a[start, end]tuple,''for an incomplete boundnormalizeTemplateDate(calendar, value, template, { min, max }): pads a single digit that cannot start a two-digit segment, clamps complete segments to the calendar, and clamps a complete value tomin/maxstepTimeSegment(value, template, position, direction, step): steps the time segment under the caret; time does not depend on the calendar, so it takes none
Common Mistakes
- Passing
Dateobjects, timestamps, numeric years, or zero-based months instead of ISO strings. - Converting values to the display calendar before storing them; store the ISO day and let the calendar format it.
- Creating a new adapter on every render.
- Giving a picker and its surrounding field different
calendarvalues.