{
  "schema": "leumas.docs.page/1",
  "id": "adapter:domain/ical",
  "slug": "adapters/domain/ical",
  "kind": "capabilities",
  "bucket": "package",
  "title": "ical — iCalendar (.ics / RFC 5545) adapter system",
  "name": "ical",
  "eyebrow": "iCalendar (.ics / RFC 5545) adapter system",
  "chip": null,
  "summary": "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...",
  "keywords": [
    "ical",
    "icalendar",
    "5545",
    "vevent",
    "vcalendar",
    "ical api",
    "createevent",
    "createcalendar"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "# ical — iCalendar (.ics / RFC 5545) adapter system\n\nPure-JS, **zero-dependency** capability pack for the **iCalendar file format** and calendar\nscheduling. It builds and parses `.ics` text (VCALENDAR / VEVENT / VALARM), constructs and expands\nRFC 5545 **RRULE** recurrence, and derives **free/busy** and **availability** from a set of events.\n\nNode built-ins only (`node:crypto` for UID generation). Deterministic: all recurrence math runs in\n**UTC** so results never drift with the host timezone. Output `.ics` uses CRLF line endings, 75-octet\nline folding, and RFC 5545 TEXT escaping.\n\n## Tools\n\n| Tool | Input (one args object) | Output |\n|---|---|---|\n| `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 |\n| `createCalendar` | `{ events: [...], prodId?, name?, method?, timeZone? }` | `{ ics, eventCount, uids, bytes }` — a full `VCALENDAR` |\n| `parse` | `{ ics }` | `{ calendar, events[], eventCount }` — events with ISO + epoch dates |\n| `rrule` | `{ options: { freq, interval, count, until, byDay, byMonthDay, byMonth, byHour, byMinute, bySetPos, weekStart } }` | `{ rrule, line, parts, summary }` |\n| `expandRecurrence` | `{ rrule, dtStart, rangeStart?, rangeEnd?, limit? }` | `{ occurrences[], count, truncated }` |\n| `freeBusy` | `{ events: [...], rangeStart?, rangeEnd? }` | `{ busy[], blockCount, totalBusyMs }` (merged busy blocks) |\n| `availability` | `{ rangeStart, rangeEnd, events?\\|busy?, workingHours?, slotMinutes?, minMinutes? }` | `{ slots[], slotCount, totalFreeMs }` (open/bookable slots) |\n| `addAlarm` | `{ ics?, alarm: { action, trigger\\|minutesBefore, description } }` | `{ ics }` with a `VALARM` injected (or a standalone `valarm`) |\n| `toDataUri` | `{ ics, filename? }` | `{ dataUri, filename, bytes }` — `data:text/calendar;…;base64,…` |\n| `duration` | `{ iso }` or `{ ms }` | `{ iso, ms }` — ISO 8601 ⇄ milliseconds |\n| `validate` | `{ ics }` | `{ valid, errors[], warnings[], eventCount }` |\n\nDates accept **ISO strings**, **epoch ms**, or **ICS date strings** (`YYYYMMDD`,\n`YYYYMMDDTHHMMSS`, `YYYYMMDDTHHMMSSZ`) interchangeably. Durations use ISO 8601 (`PT1H30M`, `P1D`).\n\n## Usage\n\n```js\nimport ical from './index.js';\n\n// 1. A weekly stand-up, every Mon/Wed/Fri at 09:00, 12 times\nconst rr = ical.adapters.rrule({ options: { freq: 'WEEKLY', byDay: ['MO', 'WE', 'FR'], count: 12 } });\n// rr.rrule === \"FREQ=WEEKLY;COUNT=12;BYDAY=MO,WE,FR\"\n\nconst dates = ical.adapters.expandRecurrence({\n  rrule: rr.rrule,\n  dtStart: '2026-01-05T09:00:00Z',\n  rangeEnd: '2026-03-01T00:00:00Z',\n});\n// dates.occurrences → [{ iso, ms, ics }, ...]\n\n// 2. Build a downloadable calendar file\nconst cal = ical.adapters.createCalendar({\n  name: 'Team',\n  events: [\n    { start: '2026-01-05T09:00:00Z', duration: 'PT30M', summary: 'Stand-up', rrule: rr.rrule,\n      alarms: [{ action: 'DISPLAY', minutesBefore: 10 }] },\n    { start: '2026-01-06', allDay: true, summary: 'Company holiday' },\n  ],\n});\nconst link = ical.adapters.toDataUri({ ics: cal.ics, filename: 'team.ics' });\n\n// 3. Find bookable 30-min slots inside Mon–Fri 9–5, avoiding existing meetings\nconst slots = ical.adapters.availability({\n  rangeStart: '2026-01-05T00:00:00Z',\n  rangeEnd:   '2026-01-06T00:00:00Z',\n  events: [{ start: '2026-01-05T10:00:00Z', end: '2026-01-05T11:00:00Z', summary: 'Sync' }],\n  workingHours: { startHour: 9, endHour: 17, days: ['MO','TU','WE','TH','FR'] },\n  slotMinutes: 30,\n});\n\n// 4. Round-trip: parse an .ics back to structured events\nconst parsed = ical.adapters.parse({ ics: cal.ics });\n```\n\n## DRY boundary (respected — do not cross)\n\nThis pack owns exactly **one** thing: the **iCalendar (.ics) format + its RRULE recurrence +\nfree/busy/availability**. It deliberately does **not** overlap its neighbours:\n\n- **`calendar`** renders a single date across 24 world calendar *systems* (Gregorian, Hebrew, Islamic\n  Hijri, Maya Long Count, …). That is calendar-system *conversion*, not file authoring. `ical` never\n  converts calendar systems.\n- **`cron`** parses human intervals (`'5m'`, `'2h'`) and fires schedule *actions* (ping/print/call).\n  That is job scheduling. `ical` produces RFC 5545 **RRULE** recurrence for calendar events, not cron\n  specs — and never executes actions.\n- **`datetime`** does date *math* (durations between dates, business-day counting, timezone\n  wall-clock, locale formatting, leap-year predicates). `ical` calls no date library and only does the\n  minimal UTC arithmetic needed to enumerate recurrence occurrences and build slots.\n\nIf you need world-calendar conversion, interval-firing, or general date math, use those packs and pass\ntheir results in.\n",
  "source": {
    "path": "shared/engines/adapters/domain/ical/README.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 5034,
    "hash": "a4f74be2501245716740c8cfaee0f122188cf55c"
  },
  "urls": {
    "html": "/p/adapters/domain/ical",
    "json": "/docs/adapters/domain/ical.json",
    "md": "/docs/adapters/domain/ical.md"
  },
  "links": {
    "composes": [],
    "usedBy": [],
    "product": [],
    "howTo": [],
    "skills": []
  }
}
