ical
iCalendar (.ics / RFC 5545) format pack, pure JS with zero dependencies: build VEVENT and VCALENDAR text (createEvent, createCalendar) with summary/location/organizer/attendees/categories, parse .ics...
ical — iCalendar (.ics / RFC 5545) adapter system
Pure-JS, zero-dependency capability pack for the iCalendar file format and calendar scheduling. It builds and parses .ics text (VCALENDAR / VEVENT / VALARM), constructs and expands RFC 5545 RRULE recurrence, and derives free/busy and availability from a set of events.
Node built-ins only (node:crypto for UID generation). Deterministic: all recurrence math runs in UTC so results never drift with the host timezone. Output .ics uses CRLF line endings, 75-octet line folding, and RFC 5545 TEXT escaping.
Tools
| Tool | Input (one args object) | Output | |
|---|---|---|---|
createEvent | `{ event: { start, end\ | duration, summary, location, organizer, attendees, categories, allDay, rrule, alarms, uid } }` (or the fields at top level) | { ics, uid, start, end, allDay, lineCount } — a VEVENT block |
createCalendar | { events: [...], prodId?, name?, method?, timeZone? } | { ics, eventCount, uids, bytes } — a full VCALENDAR | |
parse | { ics } | { calendar, events[], eventCount } — events with ISO + epoch dates | |
rrule | { options: { freq, interval, count, until, byDay, byMonthDay, byMonth, byHour, byMinute, bySetPos, weekStart } } | { rrule, line, parts, summary } | |
expandRecurrence | { rrule, dtStart, rangeStart?, rangeEnd?, limit? } | { occurrences[], count, truncated } | |
freeBusy | { events: [...], rangeStart?, rangeEnd? } | { busy[], blockCount, totalBusyMs } (merged busy blocks) | |
availability | `{ rangeStart, rangeEnd, events?\ | busy?, workingHours?, slotMinutes?, minMinutes? }` | { slots[], slotCount, totalFreeMs } (open/bookable slots) |
addAlarm | `{ ics?, alarm: { action, trigger\ | minutesBefore, description } }` | { ics } with a VALARM injected (or a standalone valarm) |
toDataUri | { ics, filename? } | { dataUri, filename, bytes } — data:text/calendar;…;base64,… | |
duration | { iso } or { ms } | { iso, ms } — ISO 8601 ⇄ milliseconds | |
validate | { ics } | { valid, errors[], warnings[], eventCount } |
Dates accept ISO strings, epoch ms, or ICS date strings (YYYYMMDD, YYYYMMDDTHHMMSS, YYYYMMDDTHHMMSSZ) interchangeably. Durations use ISO 8601 (PT1H30M, P1D).
Usage
import ical from './index.js';
// 1. A weekly stand-up, every Mon/Wed/Fri at 09:00, 12 times
const rr = ical.adapters.rrule({ options: { freq: 'WEEKLY', byDay: ['MO', 'WE', 'FR'], count: 12 } });
// rr.rrule === "FREQ=WEEKLY;COUNT=12;BYDAY=MO,WE,FR"
const dates = ical.adapters.expandRecurrence({
rrule: rr.rrule,
dtStart: '2026-01-05T09:00:00Z',
rangeEnd: '2026-03-01T00:00:00Z',
});
// dates.occurrences → [{ iso, ms, ics }, ...]
// 2. Build a downloadable calendar file
const cal = ical.adapters.createCalendar({
name: 'Team',
events: [
{ start: '2026-01-05T09:00:00Z', duration: 'PT30M', summary: 'Stand-up', rrule: rr.rrule,
alarms: [{ action: 'DISPLAY', minutesBefore: 10 }] },
{ start: '2026-01-06', allDay: true, summary: 'Company holiday' },
],
});
const link = ical.adapters.toDataUri({ ics: cal.ics, filename: 'team.ics' });
// 3. Find bookable 30-min slots inside Mon–Fri 9–5, avoiding existing meetings
const slots = ical.adapters.availability({
rangeStart: '2026-01-05T00:00:00Z',
rangeEnd: '2026-01-06T00:00:00Z',
events: [{ start: '2026-01-05T10:00:00Z', end: '2026-01-05T11:00:00Z', summary: 'Sync' }],
workingHours: { startHour: 9, endHour: 17, days: ['MO','TU','WE','TH','FR'] },
slotMinutes: 30,
});
// 4. Round-trip: parse an .ics back to structured events
const parsed = ical.adapters.parse({ ics: cal.ics });
DRY boundary (respected — do not cross)
This pack owns exactly one thing: the iCalendar (.ics) format + its RRULE recurrence + free/busy/availability. It deliberately does not overlap its neighbours:
calendarrenders a single date across 24 world calendar systems (Gregorian, Hebrew, Islamic
Hijri, Maya Long Count, …). That is calendar-system conversion, not file authoring. ical never converts calendar systems.
cronparses human intervals ('5m','2h') and fires schedule actions (ping/print/call).
That is job scheduling. ical produces RFC 5545 RRULE recurrence for calendar events, not cron specs — and never executes actions.
datetimedoes date math (durations between dates, business-day counting, timezone
wall-clock, locale formatting, leap-year predicates). ical calls no date library and only does the minimal UTC arithmetic needed to enumerate recurrence occurrences and build slots.
If you need world-calendar conversion, interval-firing, or general date math, use those packs and pass their results in.