or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

components.mdindex.mdparsing.mdrecurrence.mdtime-date.mdtimezone.mdutilities.md
tile.json

recurrence.mddocs/

Recurrence Rule Processing

Complete implementation of RFC 5545 recurrence rules with expansion engines for generating recurring event instances. These classes handle complex recurrence patterns including frequency, intervals, count limits, and various constraints.

Capabilities

Recur Class

Recurrence rule representation providing parsing, validation, and occurrence generation for RRULE properties.

/**
 * Recurrence rule representation
 * @param data - Recurrence rule data
 */
class Recur {
  constructor(data?: object);
  
  // Properties
  freq: string;           // Frequency (SECONDLY, MINUTELY, HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY)
  interval: number;       // Interval between occurrences
  wkst: number;          // Week start day (1=Sunday, 2=Monday, etc.)
  until: Time;           // End date/time
  count: number;         // Maximum number of occurrences
  bySecond: number[];    // Seconds constraint
  byMinute: number[];    // Minutes constraint  
  byHour: number[];      // Hours constraint
  byDay: string[];       // Days constraint (e.g., ["MO", "WE", "FR"])
  byMonthDay: number[];  // Month days constraint
  byYearDay: number[];   // Year days constraint
  byWeekNo: number[];    // Week numbers constraint
  byMonth: number[];     // Months constraint
  bySetPos: number[];    // Set position constraint
  
  // Static methods
  static fromString(string: string): Recur;
  static fromData(aData: object): Recur;
  static icalDayToNumericDay(string: string, aWeekStart?: number): number;
  static numericDayToIcalDay(num: number, aWeekStart?: number): string;
  
  // Instance methods
  iterator(aStart: Time): RecurIterator;
  clone(): Recur;
  isFinite(): boolean;
  isByCount(): boolean;
  addComponent(aType: string, aValue: any): void;
  setComponent(aType: string, aValues: any[]): void;
  getComponent(aType: string): any[];
  getNextOccurrence(aStartTime: Time, aRecurrenceId?: Time): Time;
  fromData(data: object): void;
  toJSON(): object;
  toString(): string;
}

Usage Examples:

import ICAL from "ical.js";

// Create recurrence rule from string
const daily = ICAL.Recur.fromString("FREQ=DAILY;COUNT=10");
console.log(daily.freq);     // "DAILY"
console.log(daily.count);    // 10

// Weekly recurrence on specific days
const weekly = ICAL.Recur.fromString("FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2");
console.log(weekly.freq);     // "WEEKLY"
console.log(weekly.byDay);    // ["MO", "WE", "FR"]
console.log(weekly.interval); // 2

// Monthly recurrence with end date
const monthly = ICAL.Recur.fromString("FREQ=MONTHLY;UNTIL=20240630T235959Z;BYMONTHDAY=15");
console.log(monthly.until.toString()); // End date
console.log(monthly.byMonthDay);       // [15]

// Create from data object
const yearly = ICAL.Recur.fromData({
  freq: "YEARLY",
  interval: 1,
  byMonth: [6, 12],  // June and December
  byMonthDay: [1],   // 1st of the month
  count: 5
});

// Check recurrence properties
console.log(daily.isFinite());  // true (has count)
console.log(daily.isByCount()); // true (ends by count, not date)

// Generate occurrences
const startTime = ICAL.Time.fromString("20231201T120000Z");
const iterator = daily.iterator(startTime);

let occurrence = iterator.next();
while (occurrence) {
  console.log(occurrence.toString());
  occurrence = iterator.next();
}

// Get single next occurrence
const nextOccurrence = daily.getNextOccurrence(startTime);
console.log(nextOccurrence.toString());

// Modify recurrence rules
daily.addComponent("byHour", 14);  // Add hour constraint
daily.setComponent("byDay", ["MO", "WE", "FR"]); // Set day constraint

// Day conversion utilities
const mondayNum = ICAL.Recur.icalDayToNumericDay("MO"); // 2
const mondayStr = ICAL.Recur.numericDayToIcalDay(2);    // "MO"

// Complex monthly recurrence (last Friday of month)
const lastFriday = ICAL.Recur.fromString("FREQ=MONTHLY;BYDAY=-1FR");
console.log(lastFriday.byDay); // ["-1FR"]

// Yearly recurrence (every 2 years on specific days)
const biYearly = ICAL.Recur.fromString("FREQ=YEARLY;INTERVAL=2;BYMONTH=6;BYMONTHDAY=15,30");
console.log(biYearly.interval);    // 2
console.log(biYearly.byMonth);     // [6]
console.log(biYearly.byMonthDay);  // [15, 30]

RecurIterator Class

Iterator for single recurrence rules providing detailed control over occurrence generation.

/**
 * Iterator for single recurrence rules
 * @param options - Iterator initialization options
 */
class RecurIterator {
  constructor(options?: object);
  
  // Properties
  rule: Recur;               // Associated recurrence rule
  dtstart: Time;             // Start date/time
  last: Time;                // Last generated occurrence
  occurrence_number: number; // Current occurrence count
  completed: boolean;        // Iterator completion status
  
  // Static constants
  static UNKNOWN = 0;   // Unknown mode
  static CONTRACT = 1;  // Contract mode
  static EXPAND = 2;    // Expand mode  
  static ILLEGAL = 3;   // Illegal mode
  
  // Instance methods
  fromData(options: object): void;
  init(): void;
  next(again?: boolean): Time | null;
  toJSON(): object;
}

Usage Examples:

import ICAL from "ical.js";

// Create iterator directly
const recur = ICAL.Recur.fromString("FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=10");
const startTime = ICAL.Time.fromString("20231201T120000Z");

const iterator = new ICAL.RecurIterator({
  rule: recur,
  dtstart: startTime
});

// Initialize and iterate
iterator.init();
let occurrence = iterator.next();
let count = 0;

while (occurrence && !iterator.completed) {
  console.log(`Occurrence ${++count}: ${occurrence.toString()}`);
  console.log(`Number: ${iterator.occurrence_number}`);
  occurrence = iterator.next();
}

// Create iterator from recurrence rule
const dailyRecur = ICAL.Recur.fromString("FREQ=DAILY;COUNT=5");
const dailyIterator = dailyRecur.iterator(startTime);

// Manual iteration control
console.log(dailyIterator.completed); // false
let dailyOccurrence = dailyIterator.next();
console.log(dailyOccurrence.toString());

// Skip to next occurrence without processing
dailyOccurrence = dailyIterator.next(true); // 'again' parameter

// Check completion status
if (dailyIterator.completed) {
  console.log("Iterator has finished generating occurrences");
}

// Serialize iterator state
const iteratorState = iterator.toJSON();
console.log(iteratorState);

// Restore iterator from state
const restoredIterator = new ICAL.RecurIterator();
restoredIterator.fromData(iteratorState);

RecurExpansion Class

Recurring rule expansion engine for managing multiple recurrence rules and exceptions, commonly used for complex event series.

/**
 * Recurring rule expansion engine
 * @param options - Expansion options
 */
class RecurExpansion {
  constructor(options?: object);
  
  // Properties
  dtstart: Time;                // Start date/time
  last: Time;                   // Last generated occurrence
  complete: boolean;            // Expansion completion status
  ruleDates: Time[];           // RDATE occurrences
  exDates: Time[];             // EXDATE exclusions
  ruleIterators: RecurIterator[]; // Rule iterators
  
  // Instance methods
  fromData(options: object): void;
  next(): Time | null;
  toJSON(): object;
}

Usage Examples:

import ICAL from "ical.js";

// Create expansion for event with multiple rules
const expansion = new ICAL.RecurExpansion({
  dtstart: ICAL.Time.fromString("20231201T120000Z"),
  // Multiple recurrence rules
  ruleIterators: [
    ICAL.Recur.fromString("FREQ=WEEKLY;BYDAY=MO,WE,FR").iterator(ICAL.Time.fromString("20231201T120000Z")),
    ICAL.Recur.fromString("FREQ=MONTHLY;BYMONTHDAY=1").iterator(ICAL.Time.fromString("20231201T120000Z"))
  ],
  // Additional specific dates
  ruleDates: [
    ICAL.Time.fromString("20231225T120000Z"), // Christmas exception
    ICAL.Time.fromString("20240101T120000Z")  // New Year exception
  ],
  // Excluded dates  
  exDates: [
    ICAL.Time.fromString("20231222T120000Z")  // Skip this occurrence
  ]
});

// Generate all occurrences
let occurrence = expansion.next();
while (occurrence && !expansion.complete) {
  console.log(occurrence.toString());
  occurrence = expansion.next();
}

// Use with Event class for complete recurrence handling
const eventComponent = new ICAL.Component("vevent");
eventComponent.addPropertyWithValue("uid", "recurring-event@example.com");
eventComponent.addPropertyWithValue("dtstart", ICAL.Time.fromString("20231201T120000Z"));
eventComponent.addPropertyWithValue("summary", "Recurring Meeting");
eventComponent.addPropertyWithValue("rrule", ICAL.Recur.fromString("FREQ=WEEKLY;BYDAY=FR;COUNT=10"));

const event = new ICAL.Event(eventComponent);

// Get expansion iterator from event
const eventExpansion = event.iterator(ICAL.Time.fromString("20231201T000000Z"));
let eventOccurrence = eventExpansion.next();

while (eventOccurrence && !eventExpansion.complete) {
  // Get occurrence details
  const details = event.getOccurrenceDetails(eventOccurrence);
  console.log(`Event at: ${eventOccurrence.toString()}`);
  console.log(`Details:`, details);
  
  eventOccurrence = eventExpansion.next();
}

// Serialize expansion state
const expansionState = expansion.toJSON();
console.log(expansionState);

// Restore expansion from state
const newExpansion = new ICAL.RecurExpansion();
newExpansion.fromData(expansionState);

Common Recurrence Patterns

import ICAL from "ical.js";

// Daily for 30 days
const daily = ICAL.Recur.fromString("FREQ=DAILY;COUNT=30");

// Every weekday (Monday through Friday)
const weekdays = ICAL.Recur.fromString("FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR");

// Every 2 weeks on Tuesday and Thursday
const biweekly = ICAL.Recur.fromString("FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,TH");

// Monthly on the 15th until end of year
const monthly15th = ICAL.Recur.fromString("FREQ=MONTHLY;BYMONTHDAY=15;UNTIL=20231231T235959Z");

// Last Friday of every month
const lastFriday = ICAL.Recur.fromString("FREQ=MONTHLY;BYDAY=-1FR");

// First and third Monday of each month
const firstThirdMonday = ICAL.Recur.fromString("FREQ=MONTHLY;BYDAY=1MO,3MO");

// Yearly on June 15th and December 25th
const yearlyHolidays = ICAL.Recur.fromString("FREQ=YEARLY;BYMONTH=6,12;BYMONTHDAY=15,25");

// Every 6 months on the 1st
const semiAnnual = ICAL.Recur.fromString("FREQ=MONTHLY;INTERVAL=6;BYMONTHDAY=1");

// Complex: Every 2 years on the 2nd Tuesday of March and October
const complex = ICAL.Recur.fromString("FREQ=YEARLY;INTERVAL=2;BYMONTH=3,10;BYDAY=2TU");