Dates and Times
Date and time components in @cfa/react-core — DatePicker, DateRangePicker, Calendar, RangeCalendar, and TimeInput — use typed objects from @internationalized/date instead of native JavaScript Date. Import date utilities from @cfa/react-core/dates and components from @cfa/react-core.
If you are used to passing new Date(...) into a date picker, that is the main thing that changes. The components themselves still work the same way — controlled or uncontrolled, with value, defaultValue, and onChange.
Quick start
import { DatePicker } from "@cfa/react-core";import { parseDate } from "@cfa/react-core/dates";
<DatePicker label="Date of Birth" defaultValue={parseDate("1990-06-15")} onChange={(date) => { console.log(date.toString()); // "1990-06-15" — send this to your API }}/>;Why not Date?
JavaScript Date combines three separate concerns into one mutable object:
- Calendar date — year, month, day
- Clock time — hour, minute, second
- Time zone — which local offset applies
That makes common tasks easy to get wrong. A birthday has no time zone. A meeting at “2:30 PM Eastern” is not the same as a UTC timestamp. Daylight saving transitions can shift or skip hours when you use native Date arithmetic.
@internationalized/date splits these into small, immutable objects. Each type carries only what it needs — no more, no less. @cfa/react-core date components are built on this model.
You do not need to learn the entire library. Most apps use one or two types and a handful of conversion patterns. The sections below cover those essentials; see the React Aria Internationalized Date docs for advanced topics.
Date types
Pick the type that matches what you are collecting. When in doubt, start with CalendarDate.
CalendarDate | CalendarDateTime | ZonedDateTime | |
|---|---|---|---|
| Represents | A calendar day | A date and time | A date and time in a specific time zone |
| Has time? | No | Yes | Yes |
| Has time zone? | No | No | Yes |
| Typical use | Birthdays, due dates, date-only pickers | Local date-and-time pickers | Scheduling across time zones, converting from native Date |
| Create from string | parseDate("2025-06-25") | parseDateTime("2025-06-25T14:30") | fromDate(d, getLocalTimeZone()) |
| Typical component | DatePicker with granularity="day" | DatePicker with granularity="minute" | Uncommon in basic forms |
Time is a fourth type for clock time without a date — used with TimeInput. Date ranges use { start, end } with CalendarDate values; see RangeCalendar and DateRangePicker.
For forms, store the parsed date object directly in state or pass it through a React Hook Form Controller — not a native Date or ISO string, and no wrapper needed.
Now and today
A common question: “I used to just call new Date() — why do I need today(getLocalTimeZone())?” They are not the same thing.
| What you need | Native | React Core | What you get |
|---|---|---|---|
| Current date and time | new Date() | now(getLocalTimeZone()) | Full timestamp with time zone |
| Today’s calendar day | No direct equivalent | today(getLocalTimeZone()) | Date only — no time |
new Date() always includes a clock time — right now that might be 2:47 PM. today(getLocalTimeZone()) returns only today’s calendar day — the right value for a date-only picker.
import { today, now, getLocalTimeZone } from "@cfa/react-core/dates";
// "What time is it right now?"now(getLocalTimeZone()); // ZonedDateTime — date, time, and time zone
// "What is today's date?" (for a date-only field)today(getLocalTimeZone()); // CalendarDate — just the dayThe extra function call is not ceremony — it makes you pick the right type:
- Need now with time? →
now(getLocalTimeZone()) - Need today’s date only? →
today(getLocalTimeZone()) - Need a specific day from your API? →
parseDate("2026-06-25")
Native Date bundles all of this into one object. That convenience is exactly why it is easy to pass the wrong thing into a date picker.
Conversions
ISO strings
The simplest API integration is to keep dates as ISO strings in your backend, database, and request payloads. In React, store the corresponding @internationalized/date object in state or form data — parse when data arrives and call .toString() before you send it back. Avoid native Date in the React Core layer when you can.
import { parseDate, parseDateTime } from "@cfa/react-core/dates";
// From your API — parse when loadingconst date = parseDate("2026-06-25");const dateTime = parseDateTime("2026-06-25T10:00:00");
// On submit — serialize back to your APIdate.toString(); // "2026-06-25"dateTime.toString(); // "2026-06-25T10:00:00"Cheat sheet
| I have… | I need… | Use |
|---|---|---|
API string "2024-10-22" | CalendarDate | parseDate("2024-10-22") |
API string "2024-10-22T14:30" | CalendarDateTime | parseDateTime("2024-10-22T14:30") |
CalendarDate or CalendarDateTime | API string | date.toString() |
native Date | CalendarDate | toCalendarDate(fromDate(d, getLocalTimeZone())) |
native Date | ZonedDateTime | fromDate(d, getLocalTimeZone()) |
| date object | native Date | date.toDate(getLocalTimeZone()) |
Legacy Date
There is no single function to convert native Date to CalendarDate. The library uses a two-step conversion — fromDate creates a ZonedDateTime, then toCalendarDate strips the time. Both are documented on the ZonedDateTime page.
Convert once at the edge of your app, then work with date objects internally:
import { fromDate, toCalendarDate, getLocalTimeZone } from "@cfa/react-core/dates";
const jsDate = new Date();
// Native Date → CalendarDatetoCalendarDate(fromDate(jsDate, getLocalTimeZone())).toString();
// Native Date → ZonedDateTimefromDate(jsDate, getLocalTimeZone());Constructors
parseDate and parseDateTime are usually the easiest way to create date values. Use the constructors when you already have separate numeric parts — for example, building a date from form fields or test fixtures.
Months are 1-indexed (June = 6, not 5 like native Date):
import { CalendarDate, CalendarDateTime, Time } from "@cfa/react-core/dates";
new CalendarDate(2025, 6, 25); // date onlynew CalendarDateTime(2025, 6, 25, 14, 30); // date and timenew Time(14, 30); // time onlyConstructors take numbers, not ISO strings — use parseDate("2025-06-25") for strings. The ZonedDateTime constructor also requires a time zone and UTC offset; prefer fromDate() or see the ZonedDateTime docs when you need one.
Manipulation
Date objects are immutable — add, subtract, and set return new values rather than mutating the original.
Add and subtract
import { parseDate } from "@cfa/react-core/dates";
const date = parseDate("2024-10-22");
date.add({ days: 7 }); // 2024-10-29date.add({ months: 1 }); // 2024-11-22date.subtract({ weeks: 2 }); // 2024-10-08The same methods work on CalendarDateTime and ZonedDateTime. Time zone-aware types adjust correctly around daylight saving transitions.
Compare
Do not compare date objects with ===. Use .compare():
const a = parseDate("2024-10-22");const b = parseDate("2024-10-25");
a.compare(b); // negative — a is before ba.compare(b) === 0; // falsea.compare(b) < 0; // trueYou can also compare .toString() output when both values are the same type.
Queries
import { today, isToday, isWeekend, getLocalTimeZone } from "@cfa/react-core/dates";
const date = parseDate("2024-10-22");
today(getLocalTimeZone()); // today's dateisToday(date, getLocalTimeZone()); // falseisWeekend(date, "en-US"); // depends on the dateSee the React Aria docs for more query and manipulation functions.
Pitfalls
Avoid toISOString()
// ❌ Uses UTC — a local date near midnight can shift to the wrong dayparseDate(jsDate.toISOString().slice(0, 10));Prefer parseDate with strings from your API, or toCalendarDate(fromDate(d, getLocalTimeZone())) when you have a native Date.
Avoid constructor for strings
// ❌ Constructor takes numbers, not stringsnew CalendarDate("2025-06-25");
// ✅parseDate("2025-06-25");new CalendarDate(2025, 6, 25);Avoid native Date in React state
// ❌ Mixing native Date with React Core componentsconst [date, setDate] = useState(new Date());
// ✅ Store the parsed date object in React stateconst [date, setDate] = useState(null); // CalendarDate | null
<DatePicker value={date} onChange={setDate} />;Month index
CalendarDate months are 1-indexed (January = 1). Native Date.getMonth() returns 0–11. Prefer parseDate() or fromDate() over manual construction from native Date.
Avoid ===
Date objects are compared by value, not reference. Use .compare() or .toString().
Time zone for toDate()
A CalendarDate represents a calendar day, not an instant. When converting to native Date, pass the intended time zone — usually getLocalTimeZone().