Skip to content

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.

CalendarDateCalendarDateTimeZonedDateTime
RepresentsA calendar dayA date and timeA date and time in a specific time zone
Has time?NoYesYes
Has time zone?NoNoYes
Typical useBirthdays, due dates, date-only pickersLocal date-and-time pickersScheduling across time zones, converting from native Date
Create from stringparseDate("2025-06-25")parseDateTime("2025-06-25T14:30")fromDate(d, getLocalTimeZone())
Typical componentDatePicker 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 needNativeReact CoreWhat you get
Current date and timenew Date()now(getLocalTimeZone())Full timestamp with time zone
Today’s calendar dayNo direct equivalenttoday(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 day

The 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 loading
const date = parseDate("2026-06-25");
const dateTime = parseDateTime("2026-06-25T10:00:00");
// On submit — serialize back to your API
date.toString(); // "2026-06-25"
dateTime.toString(); // "2026-06-25T10:00:00"

Cheat sheet

I have…I need…Use
API string "2024-10-22"CalendarDateparseDate("2024-10-22")
API string "2024-10-22T14:30"CalendarDateTimeparseDateTime("2024-10-22T14:30")
CalendarDate or CalendarDateTimeAPI stringdate.toString()
native DateCalendarDatetoCalendarDate(fromDate(d, getLocalTimeZone()))
native DateZonedDateTimefromDate(d, getLocalTimeZone())
date objectnative Datedate.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 → CalendarDate
toCalendarDate(fromDate(jsDate, getLocalTimeZone())).toString();
// Native Date → ZonedDateTime
fromDate(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 only
new CalendarDateTime(2025, 6, 25, 14, 30); // date and time
new Time(14, 30); // time only

Constructors 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-29
date.add({ months: 1 }); // 2024-11-22
date.subtract({ weeks: 2 }); // 2024-10-08

The 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 b
a.compare(b) === 0; // false
a.compare(b) < 0; // true

You 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 date
isToday(date, getLocalTimeZone()); // false
isWeekend(date, "en-US"); // depends on the date

See 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 day
parseDate(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 strings
new 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 components
const [date, setDate] = useState(new Date());
// ✅ Store the parsed date object in React state
const [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().

Further reading