@molecule/api-recurring-schedule
v1.0.1
Published
RRULE-style schedule engine: next-occurrence and expand-occurrences-in-window for recurring events (daily/weekly/monthly/yearly with interval, byday/bymonthday, count/until termination).
Readme
@molecule/api-recurring-schedule
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
RRULE-style schedule engine for molecule.dev.
Pure-function next-occurrence and expand-occurrences-in-window helpers
for recurring events: daily/weekly/monthly/yearly with interval,
byDay (weekday filter), byMonthDay (incl. negative = "from end"),
byMonth, and termination via count or until.
All math is performed in UTC so DST transitions in the host's local
zone never shift occurrences by an hour. Wall-clock-of-day from the
seed startDate is preserved across every emitted occurrence.
validateRule (called internally by both nextOccurrence and
expandOccurrences) rejects duplicate values within byDay,
byMonthDay, or byMonth (e.g. byDay: ['MO', 'MO']) — a duplicate
would otherwise silently double-emit the same instant and burn
count/maxOccurrences twice per real occurrence.
Designed for personal-finance recurring transactions, meeting scheduler, medication reminders, habit trackers, productivity recurring tasks, and other apps that need to project a rule forward without persisting state.
Quick Start
import {
expandOccurrences,
nextOccurrence,
type RecurrenceRule,
} from '@molecule/api-recurring-schedule'
const rule: RecurrenceRule = {
frequency: 'WEEKLY',
startDate: '2026-01-05T09:00:00.000Z', // a Monday
byDay: ['MO', 'WE', 'FR'],
count: 6,
}
nextOccurrence(rule, '2026-01-06T00:00:00.000Z')
// → '2026-01-07T09:00:00.000Z' (Wednesday)
expandOccurrences(rule, {
start: '2026-01-01T00:00:00.000Z',
end: '2026-02-01T00:00:00.000Z',
})
// → six ISO strings: Mon/Wed/Fri across two weeksType
utility
Installation
npm install @molecule/api-recurring-scheduleAPI
Interfaces
OccurrenceOptions
Options passed to occurrence generators.
interface OccurrenceOptions {
/**
* Hard cap on the number of occurrences *returned* by
* `expandOccurrences`. Occurrences skipped while advancing from the
* rule's seed to the requested window/lower bound do NOT count against
* it, so an old-but-unbounded rule stays answerable years after its
* `startDate`. `nextOccurrence` returns at most one occurrence and is
* only short-circuited by a value below 1. Runaway rules are defended
* separately by an internal hard iteration ceiling. Default: 1000.
*/
maxOccurrences?: number
}OccurrenceWindow
A half-open [start, end) window of ISO 8601 date-time strings used by
expandOccurrences.
interface OccurrenceWindow {
/** Inclusive lower bound (ISO 8601). */
start: string
/** Exclusive upper bound (ISO 8601). */
end: string
}RecurrenceRule
A recurrence rule describing a repeating event.
Modeled after iCalendar RRULE (RFC 5545) but simplified to the subset needed by personal-finance, meeting-scheduler, medication-reminder, habit-tracker, and productivity recurring-task apps.
interface RecurrenceRule {
/** Repetition frequency. */
frequency: Frequency
/** ISO 8601 date-time string (UTC or with offset) — the seed occurrence. */
startDate: string
/**
* Repeat every `interval` units of `frequency`. Default: 1.
* e.g. frequency=WEEKLY, interval=2 ⇒ every two weeks.
*/
interval?: number
/**
* For WEEKLY: which days of the week. e.g. `['MO','WE','FR']`.
* For MONTHLY/YEARLY: positive nth-of-month not yet supported here;
* combine with `byMonthDay` for nth-day-of-month.
* Ignored for DAILY.
*/
byDay?: Weekday[]
/**
* For MONTHLY/YEARLY: day-of-month numbers (1..31). Negative values
* count from the end of the month (-1 = last day). Out-of-range days
* for shorter months are skipped (e.g. `byMonthDay: [31]` skips Feb).
*/
byMonthDay?: number[]
/**
* For YEARLY: month numbers (1..12). When omitted, the start date's
* month is used.
*/
byMonth?: number[]
/**
* Maximum total occurrences (counting `startDate` as occurrence #1).
* When omitted, the rule is unbounded (or bounded only by `until`).
*/
count?: number
/**
* Inclusive upper bound. ISO 8601 string. When set, no occurrence is
* generated after this instant.
*/
until?: string
}Types
Frequency
RRULE frequency. Maps to the iCalendar RFC 5545 FREQ values.
type Frequency = 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY'Weekday
Day-of-week codes used by byDay. Two-letter iCalendar codes:
MO=Mon, TU=Tue, WE=Wed, TH=Thu, FR=Fri, SA=Sat, SU=Sun.
type Weekday = 'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA' | 'SU'Functions
expandOccurrences(rule, window, options)
Expands a rule into all occurrences that fall inside the half-open
window [window.start, window.end).
function expandOccurrences(
rule: RecurrenceRule,
window: OccurrenceWindow,
options?: OccurrenceOptions,
): string[]rule— The recurrence rule.window— Inclusive-start, exclusive-end ISO date-time bounds.options— Optional generator caps.
Returns: Array of ISO 8601 date-time strings (chronological).
nextOccurrence(rule, after, options)
Returns the next occurrence at or after after, or null if the rule
has terminated (via count/until) before then.
The seed startDate itself counts as the first occurrence.
function nextOccurrence(
rule: RecurrenceRule,
after?: string | Date,
options?: OccurrenceOptions,
): string | nullrule— The recurrence rule.after— Lower bound (inclusive) as ISO string orDate. When omitted, defaults to the rule'sstartDate.options— Optional generator caps.
Returns: ISO 8601 date-time string of the next occurrence, or null.
validateRule(rule)
Validates a recurrence rule. Throws Error on malformed rules.
function validateRule(rule: RecurrenceRule): voidrule— The rule to validate.
