npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ics-suite

v0.0.1

Published

Parse, query, validate and diff .ics calendar files

Downloads

139

Readme

ics-suite

A TypeScript-first library for working with .ics calendar files.

Parse, query, validate, diff, and expand iCalendar data.

import { parse, query, validate, diff, expand } from 'ics-suite'
import { Temporal } from 'temporal-polyfill'

const { calendar } = parse(icsString)

const events = query(calendar)
  .between(
    Temporal.PlainDate.from('2024-01-01'),
    Temporal.PlainDate.from('2024-01-31'),
  )
  .withAttendee('[email protected]')
  .withStatus('CONFIRMED')
  .inTimezone('Asia/Amman')
  .get()

  • Lenient parser — handles malformed files from Google, Apple, and Outlook without crashing
  • Recurrence expansion — expands RRULE into concrete instances, handling EXDATE and RECURRENCE-ID overrides correctly
  • Chainable query API — filter, sort, and transform events in a single expression
  • Structured validation — RFC 5545 rule checks with section references, not silent failures
  • Calendar diffing — compare two versions of a calendar and get exactly what changed
  • Round-trip fidelity — parse and serialize back to valid .ics without data loss

Installation

npm install ics-suite

temporal-polyfill is a required peer dependency for timezone-aware date arithmetic.


API

parse(input, options?)

Parses a raw .ics string into a typed ICSCalendar. Always succeeds — errors are collected rather than thrown.

const { calendar, errors, warnings } = parse(icsString)

if (errors.length > 0) {
  console.warn('Issues found:', errors)
}

Use parseStrict to throw on the first error instead:

const calendar = parseStrict(icsString) // throws ParseError on invalid input

Options

| Option | Type | Default | Description | |---|---|---|---| | strict | boolean | false | Throw on first error | | defaultTimezone | string | 'UTC' | Timezone for floating datetimes |


query(calendar)

Chainable API for filtering and transforming expanded events within a date window. Recurring events are expanded automatically.

const events = query(calendar)
  .between(start, end)       // set the date window — required
  .on(date)                  // shorthand for a single day
  .inclusive()               // make the end boundary inclusive
  .where(e => ...)           // custom predicate
  .withAttendee(email)       // filter by attendee
  .withOrganizer(email)      // filter by organizer
  .withStatus('CONFIRMED')   // filter by status
  .withCategory('work')      // filter by category
  .recurring()               // only recurring events
  .nonRecurring()            // only one-off events
  .inTimezone('Asia/Amman')  // convert to timezone
  .sortBy('start')           // sort field: start | end | summary
  .sortOrder('asc')          // asc | desc
  .get()                     // execute — returns ExpandedEvent[]

// Terminal methods
query(calendar).between(start, end).first()      // first match or undefined
query(calendar).between(start, end).count()      // number of matches
query(calendar).between(start, end).conflicts()  // overlapping event pairs

Each ExpandedEvent has a concrete start, end, a reference to the original event, and an isOverride flag.


validate(calendar)

Checks a parsed calendar against RFC 5545 rules. Returns structured issues with RFC section references.

const { valid, errors, warnings } = validate(calendar)

errors.forEach(e => {
  console.error(`[${e.rfc}] ${e.component} — ${e.message}`)
  // [RFC 5545, Section 3.6.1] VEVENT — missing required UID property
})
  • valid is true when there are no errors — warnings do not affect it
  • Every issue carries severity, message, rfc, component, and optionally uid and property

diff(calendarA, calendarB, options?)

Compares two calendars and returns what changed. Components are matched by UID.

const result = diff(oldCalendar, newCalendar)

result.events   // ComponentChange<ICSEvent>[]
result.todos    // ComponentChange<ICSTodo>[]
result.journals // ComponentChange<ICSJournal>[]
result.isEmpty  // true when nothing changed

Each change is one of three shapes:

{ type: 'added',    item: ICSEvent }
{ type: 'removed',  item: ICSEvent }
{ type: 'modified', before: ICSEvent, after: ICSEvent, changedFields: string[] }

DTSTAMP, LAST-MODIFIED, and SEQUENCE are excluded from comparison by default — only user-visible fields trigger a modification.

Options

| Option | Type | Default | Description | |---|---|---|---| | ignoreFields | string[] | [] | Additional fields to exclude | | includeOverrides | boolean | true | Detect changes to recurring series overrides |


expand(event, options)

Expands a single ICSEvent into concrete instances within a date window. Called automatically by query() — use directly when you need to expand a single event.

const instances = expand(event, {
  start: Temporal.PlainDate.from('2024-01-01'),
  end:   Temporal.PlainDate.from('2024-01-31'),
})

Options

| Option | Type | Default | Description | |---|---|---|---| | start | Temporal | — | Window start (required) | | end | Temporal | — | Window end (required) | | defaultTimezone | string | 'UTC' | Timezone for floating datetimes | | maxInstances | number | 1000 | Safety cap on output size | | inclusive | boolean | false | Include events on the end boundary |


License

MIT