Skip to main content
Version: 9.14.0

Date and Time Picker

DayPicker does not include a built-in time picker. However, you can implement a time picker by combining DayPicker with a time input field or any custom time picker component, as shown in the following example.

Keep date and time in sync​

Derive the time input value from the selected date so the UI stays synchronized, even when the date changes programmatically.

import React, { type ChangeEventHandler, useEffect, useState } from "react";

import { format, setHours, setMinutes } from "date-fns";
import { DayPicker } from "react-day-picker";

export function InputTime() {
const [selected, setSelected] = useState<Date>();
const [timeValue, setTimeValue] = useState<string>("00:00");

// Keep the time input in sync when the selected date changes elsewhere.
useEffect(() => {
if (selected) {
setTimeValue(format(selected, "HH:mm"));
}
}, [selected]);

const handleTimeChange: ChangeEventHandler<HTMLInputElement> = (e) => {
const time = e.target.value;
if (!selected) {
// Defer composing a full Date until a day is picked.
setTimeValue(time);
return;
}
const [hours, minutes] = time.split(":").map((str) => parseInt(str, 10));
// Compose a new Date using the current day plus the chosen time.
const newSelectedDate = setHours(setMinutes(selected, minutes), hours);
setSelected(newSelectedDate);
setTimeValue(time);
};

const handleDaySelect = (date: Date | undefined) => {
if (!timeValue || !date) {
setSelected(date);
return;
}
const [hours, minutes] = timeValue
.split(":")
.map((str) => parseInt(str, 10));
const newDate = new Date(
date.getFullYear(),
date.getMonth(),
date.getDate(),
hours,
minutes,
);
setSelected(newDate);
};

return (
<div>
<form style={{ marginBlockEnd: "1em" }}>
<label>
Set the time:{" "}
<input type="time" value={timeValue} onChange={handleTimeChange} />
</label>
</form>
<DayPicker
mode="single"
selected={selected}
onSelect={handleDaySelect}
footer={`Selected date: ${selected ? selected.toLocaleString() : "none"}`}
/>
</div>
);
}

Time zones and daylight saving​

Date values are interpreted in the user's local time zone. If you need to anchor to a specific time zone, create dates with TZDate and convert to strings (such as ISO) when persisting. Be cautious around daylight saving transitions, where some wall-clock times may be skipped or repeated.

Validate time input​

Use native <input type="time"> validation or guard values before composing a date:

const MIN_TIME = "09:00";
const MAX_TIME = "18:00";

const handleTimeChange: ChangeEventHandler<HTMLInputElement> = (e) => {
const time = e.target.value;
if (time < MIN_TIME || time > MAX_TIME) return;
setTimeValue(time);
if (selected) {
const [hours, minutes] = time.split(":").map((str) => parseInt(str, 10));
setSelected(setHours(setMinutes(selected, minutes), hours));
}
};

Date ranges with independent times​

When selecting a range, keep start and end times separate and merge them into each date:

const [from, setFrom] = useState<Date>();
const [to, setTo] = useState<Date>();
const [fromTime, setFromTime] = useState("09:00");
const [toTime, setToTime] = useState("17:00");

const applyTime = (date: Date | undefined, time: string) => {
if (!date) return undefined;
const [h, m] = time.split(":").map((str) => parseInt(str, 10));
return setHours(setMinutes(date, m), h);
};

<DayPicker
mode="range"
selected={{ from, to }}
onSelect={(range) => {
setFrom(applyTime(range?.from, fromTime));
setTo(applyTime(range?.to, toTime));
}}
/>;

Using custom time pickers​

Swap the <input type="time"> with your design system's time picker or dropdowns. Forward aria-* props to keep accessibility consistent with DayPicker, and reuse the same date-composition logic shown above.

import { DayPicker } from "react-day-picker";
import { TimePicker } from "your-design-system"; // placeholder

function CustomTimePicker() {
const [selected, setSelected] = useState<Date>();

const applyTime = (value: string) => {
if (!selected) return;
const [h, m] = value.split(":").map((str) => parseInt(str, 10));
setSelected(setHours(setMinutes(selected, m), h));
};

return (
<>
<DayPicker mode="single" selected={selected} onSelect={setSelected} />
<TimePicker
aria-label="Select time"
value={selected ? format(selected, "HH:mm") : "09:00"}
onChange={applyTime}
/>
</>
);
}

Persisting values​

When saving or sending the selection, prefer serializing to an ISO string or UTC timestamp instead of locale strings to avoid time zone ambiguity.

const payload = selected
? { startsAtIso: selected.toISOString(), startsAtMs: selected.getTime() }
: null;