rrule-temporal
Version:
Recurrence rule (rrule) processing using Temporal PlainDate/PlainDateTime, with cross-timezone and cross-calendar rrule support
248 lines (245 loc) • 9.95 kB
text/typescript
import { Temporal } from '@js-temporal/polyfill';
type Freq = 'YEARLY' | 'MONTHLY' | 'WEEKLY' | 'DAILY' | 'HOURLY' | 'MINUTELY' | 'SECONDLY';
/**
* Shared options for all rule constructors.
*/
interface BaseOpts {
/** Time zone identifier as defined in RFC 5545 §3.2.19. */
tzid?: string;
/** Safety cap when generating occurrences. */
maxIterations?: number;
/** Include DTSTART as an occurrence even if it does not match the rule pattern. */
includeDtstart?: boolean;
/** RSCALE per RFC 7529: calendar system for recurrence generation (e.g., GREGORIAN). */
rscale?: string;
/** SKIP behavior per RFC 7529: OMIT (default), BACKWARD, FORWARD (requires RSCALE). */
skip?: 'OMIT' | 'BACKWARD' | 'FORWARD';
}
/**
* Manual rule definition following the recurrence rule parts defined in
* RFC 5545 §3.3.10.
*/
interface ManualOpts extends BaseOpts {
/** FREQ: recurrence frequency */
freq: Freq;
/** INTERVAL between each occurrence of {@link freq} */
interval?: number;
/** COUNT: total number of occurrences */
count?: number;
/** UNTIL: last possible occurrence */
until?: Temporal.ZonedDateTime;
/** BYHOUR: hours to include (0-23) */
byHour?: number[];
/** BYMINUTE: minutes to include (0-59) */
byMinute?: number[];
/** BYSECOND: seconds to include (0-59) */
bySecond?: number[];
/** BYDAY: list of weekdays e.g. ["MO","WE","FR"] */
byDay?: string[];
/** BYMONTH: months of the year (1-12). With RSCALE (RFC 7529) may contain values like "5L". */
byMonth?: Array<number | string>;
/** BYMONTHDAY: days of the month (1..31 or negative from end) */
byMonthDay?: number[];
/** BYYEARDAY: days of the year (1..366 or negative from end) */
byYearDay?: number[];
/** BYWEEKNO: ISO week numbers (1..53 or negative from end) */
byWeekNo?: number[];
/** BYSETPOS: select n-th occurrence(s) after other filters */
bySetPos?: number[];
/** WKST: weekday on which the week starts ("MO".."SU") */
wkst?: string;
/** RDATE: additional dates to include */
rDate?: Temporal.ZonedDateTime[];
/** EXDATE: exception dates to exclude */
exDate?: Temporal.ZonedDateTime[];
/** DTSTART: first occurrence */
dtstart: Temporal.ZonedDateTime;
}
interface IcsOpts extends BaseOpts {
rruleString: string;
dtstart?: Temporal.ZonedDateTime;
/** COUNT: total number of occurrences, used when missing from rruleString */
count?: number;
/** UNTIL: last possible occurrence, used when missing from rruleString */
until?: Temporal.ZonedDateTime;
}
type RRuleOptions = ManualOpts | IcsOpts;
type RRuleTemporalIterator = (date: Temporal.ZonedDateTime, i: number) => boolean;
type DateFilter = Date | Temporal.ZonedDateTime;
declare class RRuleTemporal {
private readonly tzid;
private readonly originalDtstart;
private readonly opts;
private readonly maxIterations;
private readonly includeDtstart;
private static readonly rscaleCalendarSupport;
constructor(params: RRuleOptions);
private sanitizeNumericArray;
private sanitizeByDay;
private sanitizeOpts;
private rawAdvance;
/** Expand one base ZonedDateTime into all BYHOUR × BYMINUTE × BYSECOND
* combinations, keeping chronological order. If the options are not
* present the original date is returned unchanged.
*/
private expandByTime;
private nextCandidateSameDate;
private applyTimeOverride;
private computeFirst;
private matchesByDay;
private matchesByMonth;
private matchesNumericConstraint;
private matchesByMonthDay;
private matchesByHour;
private matchesByMinute;
private matchesBySecond;
private matchesAll;
private matchesByYearDay;
private getIsoWeekInfo;
private matchesByWeekNo;
options(): ManualOpts;
private cloneOptions;
private cloneUpdateOptions;
/**
* Create a new {@link RRuleTemporal} instance with modified options while keeping the current one unchanged.
*
* @example
* ```ts
* const updated = rule.with({byMonthDay: [3]});
* ```
*/
with(updates: Partial<ManualOpts>): RRuleTemporal;
private addDtstartIfNeeded;
private processOccurrences;
/**
* Returns all occurrences of the rule.
* @param iterator - An optional callback iterator function that can be used to filter or modify the occurrences.
* @returns An array of Temporal.ZonedDateTime objects representing all occurrences of the rule.
*/
private _allMonthlyByDayOrMonthDay;
private _allWeekly;
private _allMonthlyByMonth;
private _allYearlyByMonth;
private _allYearlyComplex;
private _allMinutelySecondlyComplex;
private _allMonthlyByWeekNo;
private _allMonthlyByYearDay;
private _allDailyMinutelyHourlyWithBySetPos;
private _allFallback;
/**
* Returns all occurrences of the rule.
* @param iterator - An optional callback iterator function that can be used to filter or modify the occurrences.
* @returns An array of Temporal.ZonedDateTime objects representing all occurrences of the rule.
*/
all(iterator?: RRuleTemporalIterator): Temporal.ZonedDateTime[];
/**
* RFC 7529: RSCALE present, simple monthly iteration with SKIP behavior.
* Handles month-to-month stepping from DTSTART's year/month aiming for DTSTART's day-of-month.
* Applies SKIP=OMIT (skip invalid months), BACKWARD (clamp to last day), FORWARD (first day of next month).
*/
private _allMonthlyRscaleSimple;
/**
* Converts rDate entries to ZonedDateTime and merges with existing dates.
* @param dates - Array of dates to merge with
* @returns Merged and deduplicated array of dates
*/
private mergeAndDeduplicateRDates;
/**
* Checks if a date is in the exDate list.
* @param date - Date to check
* @returns True if the date is excluded
*/
private isExcluded;
/**
* Excludes exDate entries from the given array of dates.
* @param dates - Array of dates to filter
* @returns Filtered array with exDate entries removed
*/
private excludeExDates;
/**
* Applies count limit and merges rDates with the rule-generated dates.
* @param dates - Array of dates generated by the rule
* @param iterator - Optional iterator function
* @returns Final array of dates after merging and applying count limit
*/
private applyCountLimitAndMergeRDates;
/**
* Checks if the count limit should break the loop based on rDate presence.
* @param matchCount - Current number of matches
* @returns true if the loop should break
*/
private shouldBreakForCountLimit;
/**
* Returns all occurrences of the rule within a specified time window.
* @param after - The start date or Temporal.ZonedDateTime object.
* @param before - The end date or Temporal.ZonedDateTime object.
* @param inc - Optional boolean flag to include the end date in the results.
* @returns An array of Temporal.ZonedDateTime objects representing all occurrences of the rule within the specified time window.
*/
between(after: DateFilter, before: DateFilter, inc?: boolean): Temporal.ZonedDateTime[];
/**
* Returns the next occurrence of the rule after a specified date.
* @param after - The start date or Temporal.ZonedDateTime object.
* @param inc - Optional boolean flag to include occurrences on the start date.
* @returns The next occurrence of the rule after the specified date or null if no occurrences are found.
*/
next(after?: DateFilter, inc?: boolean): Temporal.ZonedDateTime | null;
/**
* Returns the previous occurrence of the rule before a specified date.
* @param before - The end date or Temporal.ZonedDateTime object.
* @param inc - Optional boolean flag to include occurrences on the end date.
* @returns The previous occurrence of the rule before the specified date or null if no occurrences are found.
*/
previous(before?: DateFilter, inc?: boolean): Temporal.ZonedDateTime | null;
toString(): string;
private formatIcsDateTime;
private joinDates;
/**
* Given any date in a month, return all the ZonedDateTimes in that month
* matching your opts.byDay and opts.byMonth (or the single "same day" if no BYDAY).
*/
private generateMonthlyOccurrences;
/**
* Given any date in a year, return all ZonedDateTimes in that year matching
* the BYDAY/BYMONTHDAY/BYMONTH constraints. Months default to DTSTART's month
* if BYMONTH is not specified.
*/
private generateYearlyOccurrences;
private addByDay;
/**
* Helper to find the next valid value from a sorted array
*/
private findNextValidValue;
/**
* Efficiently find the next valid date for MINUTELY and SECONDLY frequency by jumping over
* large gaps when BYXXX constraints don't match.
*/
private findNextValidDate;
private applyBySetPos;
private isoWeekByDay;
/**
* Generate occurrences for a specific week number in a given year
*/
private generateOccurrencesForWeekInYear;
private getRscaleCalendarId;
private assertRscaleCalendarSupported;
private pad2;
private monthMatchesToken;
private monthsOfYear;
private startOfYear;
private endOfYear;
private rscaleFirstWeekStart;
private rscaleLastWeekCount;
private lastDayOfMonth;
private buildZdtFromPlainDate;
private rscaleMatchesByYearDay;
private rscaleMatchesByWeekNo;
private rscaleMatchesByMonth;
private rscaleMatchesByMonthDay;
private rscaleMatchesByDayBasic;
private rscaleDateMatches;
private applySkipForDay;
private generateMonthlyOccurrencesRscale;
private _allRscaleNonGregorian;
}
export { type RRuleOptions, RRuleTemporal };