Recurrence
trud-calendar supports recurring events via the recurrence property on CalendarEvent. The recurrence engine follows RFC 5545 RRULE semantics.
Basic usage
Section titled “Basic usage”Add a recurrence property to any event:
const events = [ { id: "standup", title: "Daily Standup", start: "2026-03-13T09:00:00", end: "2026-03-13T09:30:00", recurrence: { freq: "daily" }, }, { id: "review", title: "Sprint Review", start: "2026-03-13T14:00:00", end: "2026-03-13T15:00:00", recurrence: { freq: "weekly", byDay: ["FR"], count: 12, }, },];Expanding recurring events
Section titled “Expanding recurring events”The calendar does not automatically expand recurring events into instances. You must call expandRecurringEvents() from trud-calendar-core before passing events to the <Calendar> component:
import { Calendar } from "trud-calendar";import { expandRecurringEvents } from "trud-calendar-core";
function App() { const expanded = expandRecurringEvents(events, rangeStart, rangeEnd);
return <Calendar events={expanded} />;}expandRecurringEvents returns:
- All non-recurring events unchanged
- Generated instances for each recurring event that falls within the date range
Each generated instance has:
recurringEventId— theidof the parent eventoriginalDate— the date this instance was generated for (e.g.,"2026-03-17")- A synthetic
idin the formatparentId::YYYY-MM-DD
RecurrenceRule
Section titled “RecurrenceRule”interface RecurrenceRule { freq: "daily" | "weekly" | "monthly" | "yearly"; interval?: number; // Every N periods (default: 1) byDay?: RecurrenceDay[]; // "MO", "TU", "WE", "TH", "FR", "SA", "SU" byMonthDay?: number[]; // Day of month: [1, 15] bySetPos?: number[]; // Position in set: [1] = first, [-1] = last count?: number; // Stop after N occurrences until?: string; // Stop after this date (YYYY-MM-DD)}
type RecurrenceDay = "MO" | "TU" | "WE" | "TH" | "FR" | "SA" | "SU";Common patterns
Section titled “Common patterns”Every weekday (Mon–Fri)
Section titled “Every weekday (Mon–Fri)”recurrence: { freq: "weekly", byDay: ["MO", "TU", "WE", "TH", "FR"],}Every 2 weeks on Tuesday and Thursday
Section titled “Every 2 weeks on Tuesday and Thursday”recurrence: { freq: "weekly", interval: 2, byDay: ["TU", "TH"],}First Monday of every month
Section titled “First Monday of every month”recurrence: { freq: "monthly", byDay: ["MO"], bySetPos: [1],}Every year on March 15
Section titled “Every year on March 15”recurrence: { freq: "yearly",}// The date comes from the event's `start` propertyStop after 10 occurrences
Section titled “Stop after 10 occurrences”recurrence: { freq: "weekly", count: 10,}Stop after a specific date
Section titled “Stop after a specific date”recurrence: { freq: "daily", until: "2026-06-30",}Excluding dates (exDates)
Section titled “Excluding dates (exDates)”Use exDates to skip specific dates from a recurring series:
const event = { id: "standup", title: "Daily Standup", start: "2026-03-13T09:00:00", end: "2026-03-13T09:30:00", recurrence: { freq: "daily" }, exDates: ["2026-03-17", "2026-03-24"], // Skip these dates};Editing a single occurrence
Section titled “Editing a single occurrence”To edit just one instance of a recurring series:
- Add the instance’s
originalDateto the parent event’sexDatesarray - Create a new standalone event (without
recurrence) for that date with the edits
// Before: parent event recurs daily// User edits the March 17 occurrence to change the title
// 1. Add exDate to parentparentEvent.exDates = [...(parentEvent.exDates ?? []), "2026-03-17"];
// 2. Create standalone exceptionconst exception = { id: "standup::exception::2026-03-17", title: "Modified Standup", // edited title start: "2026-03-17T09:00:00", end: "2026-03-17T09:30:00", // No recurrence — this is a standalone event};Editing the entire series
Section titled “Editing the entire series”To edit all occurrences, update the parent event directly:
parentEvent.title = "New Title";parentEvent.recurrence = { freq: "weekly", byDay: ["MO", "WE"] };Since instances are generated dynamically by expandRecurringEvents(), updating the parent automatically changes all future expansions.
Backend integration
Section titled “Backend integration”The library identifies recurring series by the presence of the recurrence property on an event. When data comes from your backend:
- Store the parent event with
recurrence,exDates, etc. - Call
expandRecurringEvents(events, rangeStart, rangeEnd)to generate instances - When the user edits an instance, check
recurringEventIdto determine if it belongs to a series - Use
originalDateto know which specific occurrence was edited
// Fetch from APIconst events = await api.getEvents();
// Expand before renderingconst expanded = expandRecurringEvents(events, viewStart, viewEnd);
// Render<Calendar events={expanded} onEventClick={(event) => { if (event.recurringEventId) { // This is a recurring instance — ask user: "edit this one or all?" showScopeDialog(event); } else { // Regular event openEditModal(event); }}} />