or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

component-parser.mdcomponents.mddesign-system.mdduration.mdevents.mdhelpers.mdindex.mdparsing.mdperiod-binary.mdproperties.mdrecurrence.mdtime.mdtimezone.mdvcard-time.md
tile.json

recurrence.mddocs/

Recurrence Patterns

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.

Capabilities

Recur Class

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"

Static Factory Methods

/**
 * 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')
});

Instance Methods

/**
 * 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'

RecurIterator Class

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"

RecurExpansion Class

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)

Frequency Values

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 second

BY Rules

Detailed specification for BY* rules in recurrence patterns:

BYDAY Values:

  • MO
    ,
    TU
    ,
    WE
    ,
    TH
    ,
    FR
    ,
    SA
    ,
    SU
    - Weekdays
  • 1MO
    ,
    2TU
    , etc. - Nth occurrence of weekday in month
  • -1FR
    ,
    -2SA
    , etc. - Nth-to-last occurrence of weekday in month

BYMONTH Values:

  • 1-12
    - Month numbers (1=January, 12=December)

BYMONTHDAY Values:

  • 1-31
    - Day of month
  • -1
    to
    -31
    - Days from end of month (-1=last day)

BYYEARDAY Values:

  • 1-366
    - Day of year
  • -1
    to
    -366
    - Days from end of year

BYWEEKNO Values:

  • 1-53
    - ISO week numbers
  • -1
    to
    -53
    - Weeks from end of year

BYSETPOS Values:

  • 1-366
    - Position in set of occurrences
  • -1
    to
    -366
    - Position from end of set

Complex Recurrence Examples

const 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')
});

Working with Exceptions

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')
});