Scheduling
timeslottr has two generators: generateTimeslots for a single window, and
generateDailyTimeslots to apply a schedule across many days.
Single day
Give it a range, a slotDurationMinutes, and whatever options you need:
import { generateTimeslots } from 'timeslottr'
const slots = generateTimeslots({
day: '2024-03-15',
timezone: 'America/New_York',
range: { start: '09:00', end: '17:00' },
slotDurationMinutes: 30,
slotIntervalMinutes: 15, // start a new slot every 15 min, so slots overlap
bufferBeforeMinutes: 10, // trim 10 min off the start of the window
bufferAfterMinutes: 10, // trim 10 min off the end
excludedWindows: [{ start: '12:00', end: '13:00' }], // lunch
alignment: 'start',
})See Configuration for every option and its default.
Overlapping vs. back-to-back slots
slotIntervalMinutes is the step between slot starts. slotDurationMinutes is
how long each slot lasts. Set them equal (the default) for back-to-back slots, or
make the interval smaller to overlap them:
// 30-min slots starting every 15 min: 9:00 to 9:30, 9:15 to 9:45, and so on
generateTimeslots({
day: '2024-03-15',
range: { start: '09:00', end: '17:00' },
slotDurationMinutes: 30,
slotIntervalMinutes: 15,
})Multiple days
generateDailyTimeslots takes a period (start/end dates) plus the same config,
and applies it to each day in range:
import { generateDailyTimeslots } from 'timeslottr'
const slots = generateDailyTimeslots(
{ start: '2024-03-01', end: '2024-03-07' },
{
range: { start: '09:00', end: '17:00' }, // same hours every day
slotDurationMinutes: 60,
timezone: 'America/New_York',
},
)Per-weekday schedules
Pass a Map<Weekday, TimeslotRangeInput | null> as the range to give each day
of the week its own hours. Weekdays absent from the map (or mapped to null)
generate no slots:
import { generateDailyTimeslots, Weekday } from 'timeslottr'
const slots = generateDailyTimeslots(
{ start: '2024-03-01', end: '2024-03-14' },
{
range: new Map([
[Weekday.MON, { start: '09:00', end: '17:00' }],
[Weekday.TUE, { start: '09:00', end: '17:00' }],
[Weekday.WED, { start: '09:00', end: '12:00' }],
[Weekday.THU, { start: '09:00', end: '17:00' }],
[Weekday.FRI, { start: '10:00', end: '16:00' }],
// SAT and SUN omitted, so no slots on weekends
]),
slotDurationMinutes: 60,
timezone: 'America/New_York',
excludedWindows: [{ start: '12:00', end: '13:00' }],
},
)Weekday is an enum that matches Date.getDay(), from SUN (0) to SAT (6).
Booking windows
A slot being on the calendar doesn’t make it bookable. Nobody wants a meeting
request for ten minutes from now, or one that lands eight months out.
minimumNoticeMinutes and maximumAdvanceDays gate the generated slots against
the clock, and work with every generator:
const slots = generateDailyTimeslots(
{ start: '2024-03-01', end: '2024-06-01' },
{
range: { start: '09:00', end: '17:00' },
slotDurationMinutes: 30,
timezone: 'America/New_York',
minimumNoticeMinutes: 120, // no bookings within the next 2 hours
maximumAdvanceDays: 60, // and nothing more than 60 days out
},
)maximumAdvanceDays counts calendar days rather than 24-hour blocks, so the
cutoff stays at the same local time across a daylight-saving change. It must be
a positive whole number.
Filtering looks at each slot’s start time, never its end, so a slot already in progress is never bookable. The bookable span is half-open: a slot starting exactly at the notice boundary is kept, one starting exactly at the advance cutoff is not.
Unbookable slots are dropped before maxSlots is applied, so maxSlots: 5
gives you five bookable slots rather than five candidates that thin out to two.
Surviving slots are renumbered contiguously from 0.
Controlling “now”
Both options are measured from now, which defaults to new Date(). Set it
explicitly to make output deterministic in tests, or to gate against a server
clock instead of the caller’s:
generateTimeslots({
day: '2024-03-15',
timezone: 'UTC',
range: { start: '09:00', end: '17:00' },
slotDurationMinutes: 30,
now: '2024-03-15T10:15:00Z',
minimumNoticeMinutes: 60,
})
// earliest bookable start is 11:15, so the first slot returned is 11:30When neither option is set, now is never read and generation is a pure
function of the range.
Next, factor in who is actually free. See Multi-party availability.