Time slots, handled.

Zero-dependency TypeScript library for generating time slots with timezone support, buffers, and exclusions.

Declarative scheduling

Define single-day or multi-day schedules with one function call.

Single Day

1import { generateTimeslots } from 'timeslottr';
2
3const slots = generateTimeslots({
4 day: '2024-01-01',
5 timezone: 'America/New_York',
6 range: { start: '09:00', end: '17:00' },
7 slotDurationMinutes: 30,
8 excludedWindows: [
9 { start: '12:00', end: '13:00' }
10 ]
11});

Per-Weekday

1import { generateDailyTimeslots, Weekday } from 'timeslottr';
2
3const slots = generateDailyTimeslots(
4 { start: '2024-01-01', end: '2024-01-14' },
5 {
6 range: new Map([
7 [Weekday.MON, { start: '09:00', end: '17:00' }],
8 [Weekday.TUE, { start: '09:00', end: '17:00' }],
9 [Weekday.WED, { start: '09:00', end: '12:00' }],
10 [Weekday.THU, { start: '09:00', end: '17:00' }],
11 [Weekday.FRI, { start: '10:00', end: '16:00' }],
12 ]),
13 slotDurationMinutes: 60,
14 timezone: 'America/New_York'
15 }
16);

Try it live

Configure schedule rules and see the generated slots instantly.

Configuration

Adjust the settings to customize slot generation.

No excluded windows defined.

Generated Slots

Preview of the time slots based on your configuration.

No slots to display.

JSON Output

The raw data generated by the library.

[]

API Reference

Every exported function, config option, and type.

Core Generation Functions

generateTimeslots(config: TimeslotGenerationConfig): Timeslot[]

Generates time slots for a single day (or custom date range). This is the primary function for most use cases.

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, // overlapping 30-min slots every 15 min
bufferBeforeMinutes: 10, // 10 min buffer at start
bufferAfterMinutes: 10, // 10 min buffer at end
excludedWindows: [
{ start: '12:00', end: '13:00' } // lunch break
],
alignment: 'start',
includeEdge: true,
maxSlots: 50,
labelFormatter: (slot, index) => `Slot #${index + 1}`,
});

See the Configuration section for full details on each option.

generateDailyTimeslots(period: TimeslotRangeInput, config: DailyTimeslotConfig): Timeslot[]

Generates time slots across multiple days. Supports per-weekday schedules using a Map of Weekday to time ranges, or a single range applied to every day.

import { generateDailyTimeslots, Weekday } from 'timeslottr';
// Per-weekday schedule over a 2-week span
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 = no slots generated
]),
slotDurationMinutes: 60,
timezone: 'America/New_York',
excludedWindows: [
{ start: '12:00', end: '13:00' }
],
}
);

The first argument defines the overall date range. Days whose weekday is not in the Map (or mapped to null) are skipped. You can also pass a single TimeslotRangeInput instead of a Map to apply the same hours to every day.

Utility Functions

createTimeslot(start: Date, end: Date): Timeslot

Creates a validated Timeslot. Throws TypeError if either date is invalid, or RangeError if end is not after start.

import { createTimeslot } from 'timeslottr';
const slot = createTimeslot(
new Date('2024-03-15T09:00:00Z'),
new Date('2024-03-15T09:30:00Z')
);
// { start: Date, end: Date }
overlaps(a: Timeslot, b: Timeslot): boolean

Returns true if two timeslots overlap in time. Uses half-open interval comparison (start inclusive, end exclusive).

import { overlaps, createTimeslot } from 'timeslottr';
const a = createTimeslot(new Date('2024-03-15T09:00:00Z'), new Date('2024-03-15T10:00:00Z'));
const b = createTimeslot(new Date('2024-03-15T09:30:00Z'), new Date('2024-03-15T10:30:00Z'));
overlaps(a, b); // true
contains(slot: Timeslot, date: Date): boolean

Returns true if a date falls within the timeslot. The start is inclusive and the end is exclusive.

import { contains, createTimeslot } from 'timeslottr';
const slot = createTimeslot(
new Date('2024-03-15T09:00:00Z'),
new Date('2024-03-15T10:00:00Z')
);
contains(slot, new Date('2024-03-15T09:30:00Z')); // true
contains(slot, new Date('2024-03-15T10:00:00Z')); // false (end is exclusive)
mergeSlots(slots: Timeslot[]): Timeslot[]

Sorts slots by start time and merges any overlapping or adjacent slots into continuous blocks. Merged slots do not carry metadata from the originals.

import { mergeSlots, createTimeslot } from 'timeslottr';
const slots = [
createTimeslot(new Date('2024-03-15T09:00:00Z'), new Date('2024-03-15T10:00:00Z')),
createTimeslot(new Date('2024-03-15T09:30:00Z'), new Date('2024-03-15T11:00:00Z')),
createTimeslot(new Date('2024-03-15T13:00:00Z'), new Date('2024-03-15T14:00:00Z')),
];
const merged = mergeSlots(slots);
// [
// { start: 09:00, end: 11:00 }, // first two merged
// { start: 13:00, end: 14:00 }, // standalone
// ]
findGaps(slots: Timeslot[], range: { start: Date; end: Date }): Timeslot[]

Finds free/unbooked time periods within a range, given a list of booked slots. Useful for discovering available scheduling windows.

import { findGaps, createTimeslot } from 'timeslottr';
const booked = [
createTimeslot(new Date('2024-03-15T09:00:00Z'), new Date('2024-03-15T10:00:00Z')),
createTimeslot(new Date('2024-03-15T11:00:00Z'), new Date('2024-03-15T12:00:00Z')),
];
const gaps = findGaps(booked, {
start: new Date('2024-03-15T08:00:00Z'),
end: new Date('2024-03-15T13:00:00Z'),
});
// [
// { start: 08:00, end: 09:00 },
// { start: 10:00, end: 11:00 },
// { start: 12:00, end: 13:00 },
// ]
timeslotToJSON(slot: Timeslot): TimeslotJSON

Converts a Timeslot to a JSON-safe object with ISO 8601 string dates. Preserves metadata if present.

import { timeslotToJSON } from 'timeslottr';
const json = timeslotToJSON(slot);
// { start: "2024-03-15T09:00:00.000Z", end: "2024-03-15T09:30:00.000Z", metadata: { ... } }
timeslotFromJSON(json: TimeslotJSON): Timeslot

Parses a TimeslotJSON object back into a Timeslot with proper Date instances. Validates dates and the start < end constraint.

import { timeslotFromJSON } from 'timeslottr';
const slot = timeslotFromJSON({
start: "2024-03-15T09:00:00.000Z",
end: "2024-03-15T09:30:00.000Z",
metadata: { index: 0, durationMinutes: 30 }
});
// { start: Date, end: Date, metadata: { index: 0, durationMinutes: 30 } }

Configuration

Options for generateTimeslots(). All options except range and slotDurationMinutes are optional.

OptionTypeDefaultDescription
rangeTimeslotRangeInputrequiredStart and end boundaries for slot generation. Accepts Date objects, ISO strings, or time-only strings like "09:00".
slotDurationMinutesnumberrequiredLength of each slot in minutes. Must be a positive number.
dayDate | string-Calendar date to anchor time-only boundaries. For example, day: '2024-03-15' with range: { start: '09:00', end: '17:00' }.
slotIntervalMinutesnumberslotDurationMinutesStep size (in minutes) between the start of consecutive slots. Set lower than slotDurationMinutes to create overlapping slots.
bufferBeforeMinutesnumber0Minutes trimmed from the start of the usable window before generating slots.
bufferAfterMinutesnumber0Minutes trimmed from the end of the usable window before generating slots.
excludedWindowsTimeslotRangeInput[]-Sub-ranges to exclude from slot generation (e.g., lunch breaks, blackout periods). Overlapping exclusions are automatically merged.
timezonestring-IANA timezone identifier (e.g., "America/New_York", "Europe/London", "UTC"). Controls how time-only strings are interpreted.
alignment'start' | 'end' | 'center''start'How to handle leftover time that doesn't fill a complete slot. 'start' discards at the end, 'end' aligns slots backward from the range end, 'center' distributes leftover evenly on both sides.
minimumSlotDurationMinutesnumberslotDurationMinutesMinimum duration (in minutes) for partial/edge slots. Only relevant when includeEdge is true.
includeEdgebooleantrueWhether to include truncated edge slots (those cut short by the range boundary or exclusions) if they meet the minimum duration.
maxSlotsnumber-Hard limit on the number of slots generated. Generation stops once this count is reached.
labelFormatter(slot, index, durationMinutes) => string-Callback to attach a custom label string to each slot's metadata. Return undefined to skip.

Additional DailyTimeslotConfig options

generateDailyTimeslots() accepts all of the above options (except day, which is set automatically per day), plus:

OptionTypeDefaultDescription
rangeTimeslotRangeInput | Map<Weekday, ...>requiredEither a single time range applied to every day, or a Map<Weekday, TimeslotRangeInput | null> for per-weekday schedules. Weekdays not in the Map are skipped.
maxDaysnumber10000Safety limit on the number of calendar days to iterate. Prevents runaway loops for large date ranges.

Types

Timeslot

The core output type. Start is inclusive, end is exclusive (half-open interval).

interface Timeslot {
start: Date; // inclusive
end: Date; // exclusive
metadata?: {
index: number; // 0-based slot index
durationMinutes: number;
label?: string; // from labelFormatter
};
}

TimeslotJSON

JSON-safe version with ISO string dates. Used with timeslotToJSON() and timeslotFromJSON().

interface TimeslotJSON {
start: string; // ISO 8601
end: string; // ISO 8601
metadata?: {
index: number;
durationMinutes: number;
label?: string;
};
}

TimeslotBoundaryInput

Flexible input type for time boundaries. Supports multiple formats:

type TimeslotBoundaryInput =
| Date // JavaScript Date object
| string // ISO string or time-only ("09:00", "17:30")
| { date?: Date | string; // optional date anchor
time: string | { // time component
hour: number;
minute?: number;
second?: number;
};
};

TimeslotRangeInput

A start/end pair of boundaries:

interface TimeslotRangeInput {
start: TimeslotBoundaryInput;
end: TimeslotBoundaryInput;
}

AlignmentStrategy

type AlignmentStrategy = 'start' | 'end' | 'center';

Weekday Enum

Maps to JavaScript's Date.getDay() values (Sunday = 0).

import { Weekday } from 'timeslottr';
Weekday.SUN // 0
Weekday.MON // 1
Weekday.TUE // 2
Weekday.WED // 3
Weekday.THU // 4
Weekday.FRI // 5
Weekday.SAT // 6