timeslottr
timeslottr is a zero-dependency TypeScript library for interval arithmetic over calendar time. It turns working hours into bookable slots, subtracts already-booked events, and intersects multiple participants’ calendars to find the windows where everyone is free.
It’s the engine layer that scheduling products like Calendly and cal.com are built on — the interval math, without the calendar sync, storage, or UI.
Features
- Slot generation. Produce fixed-duration slots from a daily range or a
per-weekday
Map, with a configurable step interval (slotIntervalMinutesfor overlapping or spaced starts), leading/trailing buffers, excluded windows, edge alignment (start/end/center), and amaxSlotscap. - Availability math.
subtract(availability − busy) andintersect(overlap across N participants) run as sort-then-sweep passes —O((n + m) log m)andO(total · log)— instead of the nested loops these features are usually hand-rolled with. - Timezone & DST correct. Built on the platform
Intl.DateTimeFormatAPI, so a day that springs forward or falls back yields the right number of real slots. Nodayjs, nodate-fns, no IANA database in your bundle. - Half-open intervals. Every interval is
[start, end). A slot ending at 10:00 does not collide with one starting at 10:00, which eliminates the off-by-one errors that silently corrupt bookings. - Typed and portable. Fully typed, dual ESM/CJS, zero runtime dependencies. Runs in Node, edge runtimes, and browsers.
timeslottr is an engine, not a booking product. It hands you Date-based interval
results; calendar sync and storage stay yours.
Installation
npm
npm install timeslottrRequires Node.js ≥ 18.
Your first slots
Generate 30-minute slots for a single day, skipping a lunch break:
import { generateTimeslots } from 'timeslottr'
const slots = generateTimeslots({
day: '2024-01-01',
timezone: 'America/New_York',
range: { start: '09:00', end: '17:00' },
slotDurationMinutes: 30,
excludedWindows: [{ start: '12:00', end: '13:00' }],
})
console.log(slots.length) // 14 slots: 9 to 12 and 1 to 5, every 30 minutes
console.log(slots[0].start.toISOString())Each slot is a Timeslot with an inclusive start, an exclusive end, and some
metadata. Start and end are real Date objects.
Find a time that works for a team
Take a base schedule, remove what is already booked, and keep only the windows where everyone is free.
import { generateAvailableTimeslots } from 'timeslottr'
const slots = generateAvailableTimeslots({
day: '2024-01-01',
timezone: 'America/New_York',
range: { start: '09:00', end: '17:00' },
slotDurationMinutes: 30,
busy: [{ start: '12:00', end: '13:00' }], // already-booked
participantsBusy: [
[{ start: '09:00', end: '10:00' }], // Alice
[{ start: '16:00', end: '17:00' }], // Bob
],
})
// → 30-min slots only where the host, Alice, and Bob are all free