Recurrence Patterns
Work with weekly, biweekly, and custom calendar-week recurrence patterns
Overview
RoomKit supports three recurrence frequencies for creating recurring booking series. The RecurrenceService expands rules into concrete dates and manages the lifecycle of recurring bookings.
Frequency Types
Weekly
Repeats every week on the specified days.
const rule = {
frequency: 'weekly',
daysOfWeek: [1, 3, 5], // Monday, Wednesday, Friday
seriesStartsAt: new Date('2026-03-02'),
seriesEndsAt: new Date('2026-07-17'),
exceptionDates: [],
};
const dates = recurrenceService.expandSeries(rule);
// → Every Mon/Wed/Fri from Mar 2 to Jul 17Biweekly
Repeats every other week, starting from the first week of the series.
const rule = {
frequency: 'biweekly',
daysOfWeek: [2, 4], // Tuesday, Thursday
seriesStartsAt: new Date('2026-03-03'),
seriesEndsAt: new Date('2026-07-16'),
exceptionDates: [],
};
const dates = recurrenceService.expandSeries(rule);
// → Every other Tue/Thu from Mar 3 to Jul 16Custom (Calendar Weeks)
Repeats only on specified ISO calendar weeks. Useful for university schedules where lectures only happen in specific weeks.
const rule = {
frequency: 'custom',
daysOfWeek: [1, 3], // Monday, Wednesday
calendarWeeks: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
seriesStartsAt: new Date('2026-03-02'),
seriesEndsAt: new Date('2026-05-15'),
exceptionDates: [],
};
const dates = recurrenceService.expandSeries(rule);
// → Mon/Wed only in ISO weeks 10-20Day-of-Week Values
| Value | Day |
|---|---|
| 0 | Sunday |
| 1 | Monday |
| 2 | Tuesday |
| 3 | Wednesday |
| 4 | Thursday |
| 5 | Friday |
| 6 | Saturday |
Exception Dates
Exclude specific dates from a series (e.g., holidays):
const rule = {
frequency: 'weekly',
daysOfWeek: [1, 3, 5],
seriesStartsAt: new Date('2026-03-02'),
seriesEndsAt: new Date('2026-07-17'),
exceptionDates: [
new Date('2026-04-03'), // Good Friday
new Date('2026-04-06'), // Easter Monday
new Date('2026-05-01'), // May Day
new Date('2026-05-14'), // Ascension Day
],
};Exception dates are compared by date only (time component is ignored).
Creating Recurring Bookings
Use createRecurringBooking() to create a full series. Each expanded date gets its own booking instance linked to the recurrence rule:
const result = await recurrenceService.createRecurringBooking(
{
roomId: 'room-101',
requesterId: 'prof-mueller',
title: 'Software Engineering',
startsAt: new Date('2026-03-02T10:00:00Z'),
endsAt: new Date('2026-03-02T11:30:00Z'),
purposeType: 'LECTURE',
},
{
frequency: 'weekly',
daysOfWeek: [1],
seriesStartsAt: new Date('2026-03-02'),
seriesEndsAt: new Date('2026-07-13'),
exceptionDates: [],
},
);
console.log(result.rule.id); // Created recurrence rule
console.log(result.bookings.length); // Successfully created bookings
console.log(result.conflicts.length); // Dates that had conflictsThe time-of-day is taken from the template booking (startsAt/endsAt) and applied to each expanded date. Conflicts are collected but do not abort the entire series.
Modifying Recurring Bookings
Modify a Single Instance
Changes one instance without affecting the rest of the series. The instance is marked as MODIFIED:
await recurrenceService.modifySingle(
bookingId,
{ startsAt: new Date('2026-04-13T14:00:00Z') },
booking.version,
);Modify This and Future Instances
Splits the series at the target date:
- The original rule is truncated to end before the target date
- A new rule is created from the target date onward
- Future
ORIGINALinstances from the old rule are deleted - New instances are expanded from the new rule
const result = await recurrenceService.modifyThisAndFuture(
bookingId,
{ roomId: 'room-202' }, // Move to a different room from this date on
);
console.log(result.newRule.id); // The new rule
console.log(result.bookings.length); // Newly created instancesModify All Instances
Deletes all ORIGINAL instances and re-creates them with changes applied. MODIFIED and DETACHED instances are left untouched and returned as skipped:
const result = await recurrenceService.modifyAll(
ruleId,
{ title: 'Advanced Software Engineering' },
);
console.log(result.bookings.length); // Re-created instances
console.log(result.skipped.length); // Untouched modified/detached instancesRecurrence Modification Types
| Type | Description |
|---|---|
original | Instance matches the series pattern, untouched |
modified | Instance was individually changed via modifySingle |
detached | Instance was fully detached from the series |
When using modifyAll, only original instances are affected. modified and detached instances preserve their individual changes.