Releases
Upgrade Guide
v0.50.0

Upgrading to v0.50.0

This guide lists every breaking change in the release with the matching migration steps and code samples.

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.xv0.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: Date for day granularity, { date, time? } once a time granularity is used.
  • STimeField: Date (time-of-day only).
  • SCalendarRange / SDateRangeField / SDateRangePicker: { start, end } with null for empty bounds.
  • STimeRangeField: { start, end } of time-of-day Dates.
- 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.xv0.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 / .yeardate.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

  • DateFormatter from @internationalized/date → plain Intl.DateTimeFormat. The useDateFormatter(initialLocale, opts) composable keeps the same method surface (custom, part, toParts, fullMonth, …) but all methods take a Date.
  • parseDate('2026-04-18') → parseISO('2026-04-18') (date-fns) or createDate(2026, 4, 18).
  • parseDateTime(...) / parseZonedDateTime(...) → parseISO / new Date(...). Timezone-prefixed strings are not supported in the first release.
  • NumberFormatter / NumberParser from @internationalized/number → createNumberFormatter(locale, options) / createNumberParser(locale, options) from @vean/aria/shared, both thin wrappers over Intl.NumberFormat (parse + isValidPartialNumber keep 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