Complete recurrence rule processing with support for complex patterns, iterators, and expansion with exceptions. ICAL.js provides comprehensive support for iCalendar recurrence rules (RRULE) including iterators for generating occurrences and expansion engines that handle exceptions.
Represents iCalendar recurrence rules with various calculation and manipulation methods.
/**
* Recur constructor
* @param {Object} data - Recurrence rule data (optional)
* @param {string} data.freq - Frequency (YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY)
* @param {number} data.interval - Interval between occurrences (default: 1)
* @param {number} data.wkst - Week start day (1=Sunday, 2=Monday, etc.)
* @param {ICAL.Time} data.until - End date for recurrence
* @param {number} data.count - Maximum number of occurrences
* @param {number[]} data.bysecond - Seconds for BYSECOND rule
* @param {number[]} data.byminute - Minutes for BYMINUTE rule
* @param {number[]} data.byhour - Hours for BYHOUR rule
* @param {string[]} data.byday - Days for BYDAY rule (e.g., ['MO', 'WE', 'FR'])
* @param {number[]} data.bymonthday - Month days for BYMONTHDAY rule
* @param {number[]} data.byyearday - Year days for BYYEARDAY rule
* @param {number[]} data.byweekno - Week numbers for BYWEEKNO rule
* @param {number[]} data.bymonth - Months for BYMONTH rule
* @param {number[]} data.bysetpos - Set positions for BYSETPOS rule
*/
class ICAL.Recur {
constructor(data);
// Properties
freq: string; // Frequency value (YEARLY, MONTHLY, etc.)
interval: number; // Interval between occurrences
wkst: number; // Week start day
until: ICAL.Time; // End date
count: number; // Maximum occurrences
bysecond: number[]; // BYSECOND values
byminute: number[]; // BYMINUTE values
byhour: number[]; // BYHOUR values
byday: string[]; // BYDAY values
bymonthday: number[]; // BYMONTHDAY values
byyearday: number[]; // BYYEARDAY values
byweekno: number[]; // BYWEEKNO values
bymonth: number[]; // BYMONTH values
bysetpos: number[]; // BYSETPOS values
icaltype: string; // Always "recur"
}Usage Examples:
const ICAL = require('ical.js');
// Daily recurrence for 10 occurrences
const dailyRule = new ICAL.Recur({
freq: 'DAILY',
count: 10
});
// Weekly on Monday, Wednesday, Friday
const weeklyRule = new ICAL.Recur({
freq: 'WEEKLY',
interval: 1,
byday: ['MO', 'WE', 'FR']
});
// Monthly on the last Friday
const monthlyRule = new ICAL.Recur({
freq: 'MONTHLY',
byday: ['-1FR'] // -1 means last occurrence
});
console.log(dailyRule.toString()); // "FREQ=DAILY;COUNT=10"
console.log(weeklyRule.toString()); // "FREQ=WEEKLY;BYDAY=MO,WE,FR"/**
* Parse RRULE string into Recur object
* @param {string} str - RRULE string (without "RRULE:" prefix)
* @returns {ICAL.Recur} Parsed recurrence rule
* @throws {Error} On invalid RRULE format
*/
static fromString(str);
/**
* Create Recur from data object
* @param {Object} data - Recurrence data
* @returns {ICAL.Recur} New recurrence rule
*/
static fromData(data);Factory Examples:
const ICAL = require('ical.js');
// Parse from RRULE string
const rule1 = ICAL.Recur.fromString('FREQ=WEEKLY;BYDAY=TU,TH;COUNT=10');
console.log(rule1.freq); // 'WEEKLY'
console.log(rule1.byday); // ['TU', 'TH']
console.log(rule1.count); // 10
// Create from data
const rule2 = ICAL.Recur.fromData({
freq: 'MONTHLY',
interval: 2,
bymonthday: [15],
until: ICAL.Time.fromString('20231215T000000Z')
});/**
* Create a clone of this recurrence rule
* @returns {ICAL.Recur} Cloned recurrence rule
*/
clone();
/**
* Check if recurrence has a finite end (count or until specified)
* @returns {boolean} True if recurrence is finite
*/
isFinite();
/**
* Check if recurrence is limited by count
* @returns {boolean} True if limited by count
*/
isByCount();
/**
* Add a component to the recurrence rule
* @param {string} part - Component name (e.g., 'byday', 'bymonth')
* @param {any} value - Component value
*/
addComponent(part, value);
/**
* Set a component of the recurrence rule
* @param {string} part - Component name
* @param {any} value - Component value
*/
setComponent(part, value);
/**
* Get a component of the recurrence rule
* @param {string} part - Component name
* @returns {any} Component value
*/
getComponent(part);
/**
* Calculate next occurrence after given time
* @param {ICAL.Time} startTime - Start time for calculation
* @param {ICAL.Time} endTime - Current time to calculate from
* @returns {ICAL.Time} Next occurrence or null if none
*/
getNextOccurrence(startTime, endTime);
/**
* Convert to RRULE string
* @returns {string} RRULE string representation
*/
toString();Method Examples:
const ICAL = require('ical.js');
const rule = new ICAL.Recur({
freq: 'WEEKLY',
byday: ['MO', 'WE']
});
// Check properties
console.log(rule.isFinite()); // false (no count or until)
console.log(rule.isByCount()); // false (no count specified)
// Add components
rule.addComponent('byday', 'FR');
console.log(rule.byday); // ['MO', 'WE', 'FR']
// Set components
rule.setComponent('interval', 2);
console.log(rule.interval); // 2
// Get components
const freq = rule.getComponent('freq');
console.log(freq); // 'WEEKLY'Iterator for generating individual occurrences from a recurrence rule.
/**
* RecurIterator constructor
* @param {Object} options - Iterator options
* @param {ICAL.Recur} options.rule - Recurrence rule to iterate
* @param {ICAL.Time} options.dtstart - Start time for recurrence
* @param {Object} options.by_data - Additional BY* rule data (optional)
*/
class ICAL.RecurIterator {
constructor(options);
// Properties
last: ICAL.Time; // Last generated occurrence
completed: boolean; // True when iteration is complete
rule: ICAL.Recur; // Associated recurrence rule
dtstart: ICAL.Time; // Start time
by_data: Object; // BY* rule data
}/**
* Get next occurrence in the recurrence
* @returns {ICAL.Time|null} Next occurrence or null when complete
*/
next();
/**
* Reset iterator to beginning
*/
reset();RecurIterator Examples:
const ICAL = require('ical.js');
const rule = new ICAL.Recur({
freq: 'DAILY',
count: 5
});
const startTime = new ICAL.Time({
year: 2023,
month: 6,
day: 15,
hour: 10,
minute: 0
});
const iterator = new ICAL.RecurIterator({
rule: rule,
dtstart: startTime
});
// Generate all occurrences
const occurrences = [];
let next;
while ((next = iterator.next())) {
occurrences.push(next.clone());
}
console.log(occurrences.length); // 5
console.log(occurrences[0].toString()); // "2023-06-15T10:00:00"
console.log(occurrences[1].toString()); // "2023-06-16T10:00:00"Manages expansion of recurring events with support for exceptions (RDATE/EXDATE).
/**
* RecurExpansion constructor
* @param {Object} options - Expansion options
* @param {ICAL.Component} options.component - VEVENT component with recurrence
* @param {ICAL.Time} options.dtstart - Event start time
* @param {ICAL.Time} options.last - Last expanded time (for resuming)
* @param {Array} options.ruleIterators - Rule iterators for resuming (optional)
* @param {Array} options.ruleDates - Rule dates for resuming (optional)
* @param {Array} options.exDates - Exception dates for resuming (optional)
* @param {number} options.ruleDateInc - Rule date index for resuming (optional)
* @param {number} options.exDateInc - Exception date index for resuming (optional)
* @param {boolean} options.complete - Completion status for resuming (optional)
*/
class ICAL.RecurExpansion {
constructor(options);
// Properties
complete: boolean; // True when expansion is complete
dtstart: ICAL.Time; // Event start time
last: ICAL.Time; // Last generated occurrence
ruleIterators: ICAL.RecurIterator[]; // Array of rule iterators
ruleDates: ICAL.Time[]; // Array of RDATE instances
exDates: ICAL.Time[]; // Array of EXDATE instances
ruleDateInc: number; // Current position in ruleDates
exDateInc: number; // Current position in exDates
exDate: ICAL.Time; // Current negative date
ruleDate: ICAL.Time; // Current additional date
}/**
* Get next occurrence including exception handling
* @returns {ICAL.Time|null} Next occurrence or null when complete
*/
next(): ICAL.Time | null;
/**
* Initialize expansion from data object (for resuming expansion)
* @param {Object} options - Data object containing expansion state
*/
fromData(options: Object): void;
/**
* Serialize expansion state to JSON (for persisting/resuming expansion)
* @returns {Object} JSON object containing expansion state
*/
toJSON(): Object;RecurExpansion Examples:
const ICAL = require('ical.js');
// Create event component with recurrence
const vevent = new ICAL.Component('vevent');
vevent.addPropertyWithValue('uid', 'recurring-event@example.com');
vevent.addPropertyWithValue('dtstart', '20230615T100000Z');
vevent.addPropertyWithValue('dtend', '20230615T110000Z');
vevent.addPropertyWithValue('summary', 'Daily Meeting');
vevent.addPropertyWithValue('rrule', 'FREQ=DAILY;COUNT=10');
// Add exceptions
vevent.addPropertyWithValue('exdate', '20230617T100000Z'); // Skip June 17
const startTime = ICAL.Time.fromString('20230615T100000Z');
const endTime = ICAL.Time.fromString('20230615T110000Z');
const expansion = new ICAL.RecurExpansion({
component: vevent,
dtstart: startTime,
dtend: endTime
});
// Generate occurrences (will skip the exception)
const occurrences = [];
let next;
while ((next = expansion.next())) {
occurrences.push(next.clone());
}
console.log(occurrences.length); // 9 (10 minus 1 exception)Standard recurrence frequencies supported:
// Frequency constants
ICAL.Recur.YEARLY: "YEARLY"; // Yearly recurrence
ICAL.Recur.MONTHLY: "MONTHLY"; // Monthly recurrence
ICAL.Recur.WEEKLY: "WEEKLY"; // Weekly recurrence
ICAL.Recur.DAILY: "DAILY"; // Daily recurrence
ICAL.Recur.HOURLY: "HOURLY"; // Hourly recurrence
ICAL.Recur.MINUTELY: "MINUTELY"; // Every minute
ICAL.Recur.SECONDLY: "SECONDLY"; // Every secondDetailed specification for BY* rules in recurrence patterns:
BYDAY Values:
MOTUWETHFRSASU1MO2TU-1FR-2SABYMONTH Values:
1-12BYMONTHDAY Values:
1-31-1-31BYYEARDAY Values:
1-366-1-366BYWEEKNO Values:
1-53-1-53BYSETPOS Values:
1-366-1-366const ICAL = require('ical.js');
// Every weekday (Monday through Friday)
const weekdayRule = new ICAL.Recur({
freq: 'WEEKLY',
byday: ['MO', 'TU', 'WE', 'TH', 'FR']
});
// First Monday of every month
const firstMondayRule = new ICAL.Recur({
freq: 'MONTHLY',
byday: ['1MO']
});
// Last Friday of every month
const lastFridayRule = new ICAL.Recur({
freq: 'MONTHLY',
byday: ['-1FR']
});
// Every other Tuesday and Thursday for 20 occurrences
const biweeklyRule = new ICAL.Recur({
freq: 'WEEKLY',
interval: 2,
byday: ['TU', 'TH'],
count: 20
});
// Quarterly on the 15th
const quarterlyRule = new ICAL.Recur({
freq: 'MONTHLY',
interval: 3,
bymonthday: [15]
});
// Every 6 months on the last day of the month
const semiannualRule = new ICAL.Recur({
freq: 'MONTHLY',
interval: 6,
bymonthday: [-1]
});
// Annual on specific date until 2025
const annualRule = new ICAL.Recur({
freq: 'YEARLY',
bymonth: [6],
bymonthday: [15],
until: ICAL.Time.fromString('20251215T000000Z')
});const ICAL = require('ical.js');
// Create recurring event
const vevent = new ICAL.Component('vevent');
vevent.addPropertyWithValue('dtstart', '20230615T100000Z');
vevent.addPropertyWithValue('rrule', 'FREQ=WEEKLY;BYDAY=MO,WE,FR');
// Add single exception date
vevent.addPropertyWithValue('exdate', '20230619T100000Z');
// Add multiple exception dates
const exdates = vevent.addProperty(
new ICAL.Property('exdate', vevent)
);
exdates.setValues([
ICAL.Time.fromString('20230621T100000Z'),
ICAL.Time.fromString('20230623T100000Z')
]);
// Add additional occurrence dates
vevent.addPropertyWithValue('rdate', '20230620T100000Z');
// Create expansion with exceptions
const expansion = new ICAL.RecurExpansion({
component: vevent,
dtstart: ICAL.Time.fromString('20230615T100000Z')
});