rrule
A JavaScript/TypeScript library for computing iCalendar (RFC 5545) recurrence rules for calendar dates.
Repository Health
Technical Analysis
rrule is a JavaScript/TypeScript port of Python’s python-dateutil recurrence engine, implementing the iCalendar RFC 5545 RRULE specification for computing recurring calendar dates. It exposes an RRule class for single recurrence rules, an RRuleSet class for combining multiple rules with explicit inclusion/exclusion dates, and an rrulestr parser that turns an iCalendar RRULE string directly into a usable rule object.
Beyond RFC compliance, the library adds natural-language conversion in both directions: toText()/fromText() translate a rule like “every 5 weeks on Monday, Friday until January 31, 2013” into RRule options and back, which is useful for building human-facing recurrence pickers without hand-rolling the parsing logic. It ships as pure TypeScript with a single runtime dependency (tslib), targeting both browser and Node environments, and is widely used as the recurrence engine behind calendar and scheduling UIs.
What You Get
- An RRule class covering the full RFC 5545 option set (freq, interval, count, until, byweekday, bymonth, bymonthday, byyearday, byweekno, bysetpos, byhour/minute/second, byeaster)
- An RRuleSet class for composing multiple RRULEs plus explicit RDATE/EXDATE inclusion and exclusion dates into one combined occurrence set
- An rrulestr() parser that converts an iCalendar RRULE/RRuleSet string directly into a working rule object, and toString()/optionsToString() for the reverse
- Natural-language conversion via toText()/fromText() and parseText(), translating rules to and from human-readable phrases like “every 5 weeks on Monday, Friday until January 31, 2013”
- IANA timezone support via the Intl API through the tzid option, plus a datetime() helper for constructing dates in the library’s expected UTC-based format
- Built-in occurrence caching so repeated all()/between() calls on the same rule don’t re-run the iteration algorithm
Common Use Cases
- Powering recurrence pickers in calendar and scheduling UIs, where a user picks “every 2 weeks on Tue/Thu” and the app needs the resulting occurrence dates
- Parsing and re-serializing iCalendar RRULE strings when importing/exporting .ics calendar data
- Computing the next N occurrences of a recurring task, billing cycle, or reminder for background job scheduling
- Converting recurrence rules to and from human-readable text for calendar UIs that display rules in plain language
Under The Hood
Architecture
Execution flows from the public RRule class (src/rrule.ts), which parses and normalizes constructor options via parseOptions/initializeOptions (src/parseoptions.ts) into a ParsedOptions object, then delegates all query methods (all, between, before, after) to a shared _iter call into the standalone iter() function (src/iter/index.ts). That iterator walks calendar periods year-by-month using an Iterinfo helper (src/iterinfo/index.ts, with easter/month/year-specific submodules) to build day masks and time sets, filtering days against every by* constraint before emitting accepted dates through an IterResult/CallbackIterResult accumulator (src/iterresult.ts, src/callbackiterresult.ts). RRuleSet (src/rruleset.ts) layers on top by merging multiple RRule/EXRULE instances with explicit RDATE/EXDATE arrays and de-duplicating the combined output, while rrulestr/parsestring/optionsToString (src/rrulestr.ts, src/parsestring.ts, src/optionstostring.ts) handle iCalendar string round-tripping and a separate src/nlp/ module handles natural-language conversion — a clean layered separation between option parsing, core iteration, set composition, string serialization, and natural-language translation, with a Cache class (src/cache.ts) memoizing repeated queries per rule instance. The core abstraction to protect is the day/time-mask iteration in iter() and Iterinfo; changing it touches every query method and both serialization paths at once.
Tech Stack
The library is authored entirely in TypeScript (98%+ of source bytes) with a single runtime dependency, tslib, keeping the published bundle dependency-free for consumers. The build pipeline compiles with tsc against tsconfig.build.json and bundles with Webpack 5 (webpack.config.js) plus terser-webpack-plugin for minification, publishing both an ES5 CommonJS build (dist/es5) and an ESM build (dist/esm) with generated .d.ts types. Linting runs through ESLint 8 with @typescript-eslint and eslint-plugin-jsdoc/eslint-plugin-import, formatting through Prettier 2, both wired into husky + lint-staged pre-commit hooks. There is no database or network layer — the library is pure computation over JS Date objects, with the Intl API used for IANA timezone resolution (tzid option) rather than a timezone library dependency.
Code Quality
Testing uses Jest with ts-jest (jest.config.js) plus mockdate for deterministic date-based assertions, and the test/ directory mirrors the src/ module layout with a dedicated test file per module (rrule, rruleset, rrulestr, parseoptions, parsestring, cache, datetime, helpers, nlp) plus shared fixtures in test/lib. CI (.github/workflows/nodejs.yml) runs the full suite across three Node versions and, notably, across five different LANG/TZ combinations (including Pacific/Kiritimati and Africa/Nairobi) specifically to catch timezone- and locale-sensitive date bugs, then reports coverage to Codecov via nyc. Source files carry substantial inline JSDoc-style comments on public methods (parameter/return documentation on every RRule query method), and the constructor/option-parsing path validates inputs and throws explicit Errors (e.g. RRule.between/before/after reject invalid Date arguments) rather than silently returning bad data.
API Design
The public surface is intentionally small and RFC-mirroring: a single RRule constructor takes an options object whose keys map directly to iCalendar RRULE parameters (freq, interval, byweekday, etc.), so anyone already familiar with the RFC or with Python’s dateutil.rrule (which this library is explicitly ported from) can use it with minimal ramp-up. Constants like RRule.MO/RRule.WEEKLY avoid magic numbers, and the datetime() helper sidesteps a documented JavaScript Date/timezone footgun that the README calls out at length. The main friction point for newcomers is exactly that UTC-handling caveat — the README devotes a long, explicit section to it because getting it wrong silently produces off-by-hours results — and the natural-language toText()/fromText() conversion is explicitly approximate rather than complete, which the library documents rather than hides.
Used by 11 apps in this directory
TaskView
Productivity · Project Management
Self-hosted project and task management platform for software teams, with Kanban boards, sprints, time tracking, GitHub/GitLab integrations, and an MCP server for AI assistants.
Yaffa
Invoicing Finance
Self-hosted personal finance app for long-term financial planning with AI-powered transaction parsing and investment tracking.