My App
CourseKitBackend

RecurrenceService

Parse, validate, and materialize RFC 5545 recurrence rules

Overview

RecurrenceService handles RRULE parsing, validation, and expansion of recurring events into concrete occurrences. It uses the rrule library internally.

import { RecurrenceService } from '@hfu.digital/coursekit-nestjs';

Methods

parseRule

Parse an RRULE string into an RRule instance.

parseRule(rruleString: string, dtstart: Date): RRule
ParameterTypeDescription
rruleStringstringRFC 5545 RRULE string (e.g., 'FREQ=WEEKLY;BYDAY=MO')
dtstartDateThe start date for the recurrence series

Throws if the RRULE string is invalid.

const rule = recurrence.parseRule('FREQ=WEEKLY;BYDAY=MO,WE,FR', new Date('2026-03-02'));

validateRule

Validate an RRULE string without parsing it fully.

validateRule(rruleString: string): string | null

Returns null if valid, or an error message string if invalid.

const error = recurrence.validateRule('FREQ=WEEKLY;BYDAY=MO');
// null (valid)

const error2 = recurrence.validateRule('FREQ=INVALID');
// "Invalid frequency: INVALID"

materialize

Expand a recurring event into concrete occurrences for a date range. Applies all exceptions (cancellations, modifications, additions).

materialize(
    event: TimetableEvent,
    exceptions: EventException[],
    dateRange: DateRange,
): MaterializedOccurrence[]
ParameterTypeDescription
eventTimetableEventThe event to expand
exceptionsEventException[]Exceptions to apply
dateRangeDateRangeThe date range to expand within

Returns an array of MaterializedOccurrence sorted by start time.

Exception handling:

  • cancelled exceptions are added as EXDATEs (occurrence removed)
  • modified exceptions override start time, duration, room, or metadata
  • added exceptions create extra occurrences
const occurrences = recurrence.materialize(weeklyLecture, exceptions, {
    start: new Date('2026-03-01'),
    end: new Date('2026-03-31'),
});
// Returns ~4 Monday occurrences, minus cancellations, plus modifications

materializeSingle

For a non-recurring event, return a single occurrence if it falls within the date range.

materializeSingle(
    event: TimetableEvent,
    dateRange: DateRange,
): MaterializedOccurrence | null

Returns null if the event does not overlap with the date range.

On this page