Core Concepts
A few ideas are worth understanding before you go further: half-open intervals,
the Timeslot shape, and how time zones are handled.
Half-open intervals [start, end)
Every range, slot, exclusion, and availability window is half-open. The start
is inclusive and the end is exclusive.
import { contains, createTimeslot } from 'timeslottr'
const slot = createTimeslot(
new Date('2024-01-01T09:00:00Z'),
new Date('2024-01-01T10:00:00Z'),
)
contains(slot, new Date('2024-01-01T09:00:00Z')) // true (start is inclusive)
contains(slot, new Date('2024-01-01T10:00:00Z')) // false (end is exclusive)This is what stops adjacent slots from overlapping. A 9:00 to 10:00 slot and a
10:00 to 11:00 slot both touch 10:00, but the first one excludes its end, so they
do not conflict. overlaps, subtract, and intersect all follow the same
rule, so booked time that ends exactly when a slot begins does not block it.
The Timeslot shape
generateTimeslots and the other generators return Timeslot[]:
interface Timeslot {
start: Date // inclusive
end: Date // exclusive
metadata?: {
index: number // 0-based position in the result
durationMinutes: number
label?: string // from labelFormatter, if provided
}
}start and end are real Date instants, stored as UTC. To send them over the
wire, use timeslotToJSON and timeslotFromJSON. Plain
JSON.stringify turns the dates into strings and will not parse them back into
Date objects.
Timezones and DST
You can write a boundary as a time-only string ('09:00'), a full ISO string, a
Date, or a { date, time } object. When you pass a timezone such as
'America/New_York', time-only boundaries are resolved in that zone and
converted to the correct UTC instant. This runs on the platform Intl API, so
there is no date library involved.
Daylight saving is handled for you. On a spring-forward day the 2:00 to 3:00 wall-clock hour does not exist, so a 00:00 to 06:00 range produces the right number of real slots:
const slots = generateTimeslots({
day: '2024-03-10', // US spring-forward
timezone: 'America/New_York',
range: { start: '00:00', end: '06:00' },
slotDurationMinutes: 30,
})
slots.length // 10: six wall-clock hours, but only five real onesDurations are measured in real elapsed time. Every 30-minute slot above is exactly 30 minutes long, including the one that spans the DST gap.
Time-only boundaries need a calendar date to anchor them. Pass day for a single
day, or let generateDailyTimeslots set it per day. See the
scheduling guide.