node-ical

A feature-rich RFC 5545 iCalendar/ICS parser for Node.js with robust RRULE recurrence expansion, timezone handling, and exception-date support.

Library
npm
v0.27.1
170stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
77/100Good
Development Activity84
Maintenance64
Community80
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
80/100Excellent
Architecture82
Code Quality88
Innovation68
Learning Curve80

node-ical is a Node.js library for parsing iCalendar/ICS files (RFC 5545), originally forked from ical.js and substantially extended since. It provides synchronous, asynchronous (promise and callback), and autodetect APIs for parsing ICS data from strings, local files, and remote URLs, so it fits both blocking scripts and event-loop-sensitive server code.

Beyond basic VEVENT/VTODO/VJOURNAL parsing, node-ical implements a full recurrence-rule (RRULE) engine with timezone-aware date calculation, dual-key EXDATE (exception date) lookups, and RECURRENCE-ID override handling that follows RFC 5545 SEQUENCE semantics for conflicting updates. A dedicated expandRecurringEvent() helper expands a recurring event into concrete instances over a date range, correctly applying exclusions, overrides, and DST transitions.

The package ships as ESM-first with CommonJS compatibility via a generated .cjs build, full TypeScript typings, and a Windows-timezone mapping table for cross-platform timezone resolution. It has no runtime dependency on a full calendar stack, relying only on the Temporal polyfill and a purpose-built RRULE implementation (rrule-temporal).

What You Get

  • Sync, async (promise/callback), and autodetect parsing APIs for strings, files, and remote URLs
  • Full RRULE recurrence expansion via the purpose-built rrule-temporal engine, timezone-aware from DTSTART
  • EXDATE and RECURRENCE-ID handling with dual date-key and full-ISO-timestamp lookups, RFC 5545 SEQUENCE-aware overrides
  • An expandRecurringEvent() helper that materializes recurring events into concrete, DST-safe instances over a date range
  • ESM-first entry point with generated CommonJS build and bundled TypeScript typings
  • Windows timezone name mapping for correctly resolving non-IANA TZIDs found in calendars exported from Outlook and similar tools

Common Use Cases

  • Importing calendar feeds (Google Calendar, Outlook, CalDAV exports) into a Node.js backend
  • Building calendar-sync integrations that need correct recurrence and exception-date semantics
  • Rendering upcoming-events lists (e.g. conference or meetup schedules) from a public .ics feed
  • Home-automation and dashboard projects that consume family or work calendars for scheduling logic
  • Validating or normalizing ICS files generated by other systems before re-export

Under The Hood

Architecture The package separates a thin entrypoint layer from the actual parsing engine: node-ical.js (ESM) and a generated node-ical.cjs both wire together ical.js (the RFC 5545 token/line parser and object-handler registry) with small focused helpers under lib/core-api.js builds the sync/async/autodetect API surface around a single parseICSImpl, public-api.js assembles the final exported object so the two entrypoints never drift, expand-recurring-event.js turns a parsed recurring VEVENT plus a date range into concrete instances, and tz-utils.js plus windowsZones.json resolve IANA and Windows-style timezone identifiers. This factoring means the core parser is entrypoint-agnostic — CJS/ESM parity and callback/promise duality are handled entirely at the wrapper layer, so a change to parsing logic can’t silently diverge between the two module formats.

Tech Stack Runtime dependencies are deliberately minimal: rrule-temporal for RFC 5545 recurrence-rule expansion and temporal-polyfill to back date/time arithmetic ahead of native Temporal support, with Node 22+ required. Dev tooling is comprehensive — xo (ESLint preset) for linting, mocha for the test runner, esbuild to produce the CommonJS build from the ESM source, tsc to validate the bundled .d.ts typings against test/tsconfig.json, and simple-git-hooks/lint-staged to enforce lint-on-commit and full-test-on-push. GitHub Actions CI runs the same npm test pipeline (build:cjs → type-check → lint → mocha) on every push.

Code Quality Testing is extensive and specific rather than generic — the test/ directory has 20+ dedicated suites (e.g. expand-recurring-event.test.js, tz-utils.test.js, google-calendar-until-bug.test.js, issue-459-exchange-recurrence-id.test.js, non-utc-until.test.js) totaling roughly 4,800 lines, each asserting against real fixture .ics files rather than synthetic mocks, plus a commonjs-compat.test.js and module-formats.test.js pair that guard the dual ESM/CJS publishing story. Error handling favors explicit rejects/throws over silent swallowing (e.g. fromURLAsync throws on a non-ok fetch response with the status text preserved), and the codebase is fully typed via a hand-maintained node-ical.d.ts checked by tsc in CI rather than inferred.

What Makes It Unique Most ICS parsers stop at flat event extraction; node-ical’s differentiator is a genuinely RFC-5545-faithful recurrence layer — SEQUENCE-aware RECURRENCE-ID conflict resolution (a newer override only replaces an older one if its SEQUENCE is greater or equal), dual-key EXDATE storage so both date-only and precise multiple-per-day exclusions resolve correctly, and DST-safe instance expansion via expandRecurringEvent(). Building this on a purpose-built rrule-temporal package (rather than a general date library retrofitted for recurrence) is an unusual and deliberate architectural choice for the npm ICS-parsing space.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search