Upgrade Guide: Date engine → date-fns + Date
v0.50.0 replaces @internationalized/date (and @internationalized/number) with date-fns and the platform Intl APIs. The public value model is now plain Date objects — there is no CalendarDate/ZonedDateTime adapter layer. The first release only supports local dates; timezone-aware values are out of scope (a separate proposal may follow).
1. Value model mapping
| v0.40.x | v0.50.0 |
|---|---|
CalendarDate (calendar day) | Date at local 00:00 |
CalendarDateTime (date + time) | { date: Date, time: Date } (DateTimeValue) |
Time (time of day) | Date whose hour/minute/second parts are meaningful (TimeValue) |
DateRange { start?: DateValue, end?: DateValue } | { start?: DateValue | null, end?: DateValue | null } |
TimeRange { start?: TimeValue, end?: TimeValue } | { start?: Date | null, end?: Date | null } |
DateMatcher / TimeMatcher | (date: Date) => boolean / (time: Date) => boolean |
The exported type names (DateValue, DateRange, TimeValue, TimeRange, …) are unchanged — only their shapes changed. A plain Date is always a valid DateValue; the { date, time? } object is required once a time part exists. Time values are normalized onto a fixed local epoch (2000-01-01), so only their time-of-day parts are meaningful.
- import { CalendarDate, CalendarDateTime, Time } from '@internationalized/date';
- const day = new CalendarDate(2026, 4, 19);
- const dateTime = new CalendarDateTime(2026, 4, 19, 14, 30);
- const time = new Time(9, 30);
+ import { createDate, createTime } from '@vean/aria/date';
+ const day = createDate(2026, 4, 19); // local midnight, 1-based month
+ const dateTime = { date: createDate(2026, 4, 19), time: createTime(14, 30) };
+ const time = createTime(9, 30);Or without helpers: new Date(2026, 3, 19) (note date-fns-style zero-based month in the native constructor).
2. Field value shapes
SDateField/SDatePicker:Datefor day granularity,{ date, time? }once a time granularity is used.STimeField:Date(time-of-day only).SCalendarRange/SDateRangeField/SDateRangePicker:{ start, end }withnullfor empty bounds.STimeRangeField:{ start, end }of time-of-dayDates.
- const range = { start: new CalendarDate(2026, 4, 18), end: undefined };
+ const range = { start: createDate(2026, 4, 18), end: null };3. Calendar math and comparison
@internationalized/date instance methods are gone. Use date-fns functions, the kernel helpers from @vean/aria/date, or native Date accessors:
| v0.40.x | v0.50.0 |
|---|---|
date.add({ days: 1 }) | addDays(date, 1) (date-fns) |
date.subtract({ months: 1 }) | addMonths(date, -1) |
date.set({ day: 1, month: 2 }) | setSegmentParts(date, { day: 1, month: 2 }) (1-based month, day clamps) |
date.cycle('day', 1) | cycleSegment(date, 'day', 1) |
date.copy() | cloneDateValue(value) / new Date(date.getTime()) |
date.toString() (ISO yyyy-MM-dd) | toISODateString(date) |
date.compare(other) | toDate(a).getTime() - toDate(b).getTime() or isBefore/isAfter helpers |
isSameDay(a, b) etc. | Same names, now exported from @vean/aria/date |
.day / .month / .year | date.getDate() / date.getMonth() + 1 / date.getFullYear() |
Note the clamping semantics: setSegmentParts clamps the day-of-month to the target month length (Jan 31 + month: 2 → Feb 28), matching the previous behavior, while addDays/addMonths roll over like date-fns always does.
4. Formatting and parsing
DateFormatterfrom@internationalized/date→ plainIntl.DateTimeFormat. TheuseDateFormatter(initialLocale, opts)composable keeps the same method surface (custom,part,toParts,fullMonth, …) but all methods take aDate.parseDate('2026-04-18')→parseISO('2026-04-18')(date-fns) orcreateDate(2026, 4, 18).parseDateTime(...)/parseZonedDateTime(...)→parseISO/new Date(...). Timezone-prefixed strings are not supported in the first release.NumberFormatter/NumberParserfrom@internationalized/number→createNumberFormatter(locale, options)/createNumberParser(locale, options)from@vean/aria/shared, both thin wrappers overIntl.NumberFormat(parse+isValidPartialNumberkeep their contracts).
5. Timezone
ZonedDateTime, CalendarIdentifier, non-gregorian calendars, toDate(value, timeZone) conversions, and the timeZoneName segment are removed. hideTimeZone stays as a no-op prop for one release cycle. If you need timezone-aware pickers today, convert at your app boundary (e.g. keep UTC Dates and offset at display time) — a first-class timezone proposal may follow separately.
6. Checklist
date-fns is imported through subpath modules (date-fns/addDays, date-fns/isSameDay, …) rather than the barrel export, so environments without sideEffects-aware tree-shaking (webpack 4, CJS SSR, Jest) only load the functions actually used. Follow the same style in your own code:
# The old packages should be gone from your dependencies
pnpm remove @internationalized/date @internationalized/number
pnpm add date-fns# No runtime references should remain
grep -rn "@internationalized" src/
# Expect: no output