ICS / iCal / iCalendar parser / expander
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
test Add breaking test case with open end limit May 19, 2018
.eslintrc initial Mar 29, 2017
.gitignore add coverage to gitignore May 20, 2017
.travis.yml Don't support node 4 anymore May 19, 2018
LICENSE initial Mar 29, 2017
README.md fix typos May 19, 2018
compile-zones.js initial Mar 29, 2017
index.js Refactor to reduce code duplication May 19, 2018
package.json 2.0.0 May 19, 2018
zones-compiled.json initial Mar 29, 2017
zones.json initial Mar 29, 2017


ical-expander 📅💥 npm version Build Status Coverage Status Known Vulnerabilities

ICS / iCal / iCalendar parser / expander.

Wrapper around ical.js that automatically handles EXDATE (excluded recursive occurrences), RRULE and recurring events overridden by RECURRENCE-ID.

Also handles timezones, and includes timezones from the IANA Time Zone Database, so that it parses correctly when a timezone definition is not available in the ICS file itself.

Be careful as the processing done in this library is synchronous and will block the JS event loop while processing. Especially when processing large ICS files and with high maxIterations values.


npm install ical-expander

⚠️ Warning: This package uses ES6 features, and might require transpiling if used in browsers


Download .ics from google calendar for example.

const IcalExpander = require('ical-expander');
const fs = require('fs');

const ics = fs.readFileSync('./basic.ics', 'utf-8');

const icalExpander = new IcalExpander({ ics, maxIterations: 100 });
const events = icalExpander.between(new Date('2017-01-24T00:00:00.000Z'), new Date('2017-03-30T00:00:00.000Z'));

const mappedEvents = events.events.map(e => ({ startDate: e.startDate, summary: e.summary }));
const mappedOccurrences = events.occurrences.map(o => ({ startDate: o.startDate, summary: o.item.summary }));
const allEvents = [].concat(mappedEvents, mappedOccurrences);

console.log(allEvents.map(e => `${e.startDate.toJSDate().toISOString()} - ${e.summary}`).join('\n'));



const icalExpander = new IcalExpander({ ics, maxIterations })
  • ics: String containing ICS data to parse
  • maxIterations: Max iterations on each RRULE. Defaults to 1000 (undefined or null). 0 means never stop (be careful!)


icalExpander.between(after, before)

Include all events occuring between after and before. i.e. with a start time before before JS Date and an end time after JS Date.

  • after JS Date: Start of range. Default: No start limit.
  • before JS Date. End of range. Default: No end limit. Do not run with no end limit and maxIterations: 0

Example of events included between a date range:

range:    ---------- after |-----------------------| before -------------
event 1:                   |          start <------|------------> end
event 2:         start <---|---------------> end   |
event 3:      start <------|-----------------------|--------------> end
event 4:                   |    start <---> end    |                                                   

Example of events not included between a date range:

range:    -------------------- after |--------| before ------------------
event 5:  start <------> end         |        |
event 6:                             |        |          start <---> end


icalExpander.before(before) is an alias for icalExpander.between(undefined, before)


icalExpander.after() is an alias for icalExpander.between(after)


icalExpander.all() is an alias for icalExpander.between()
Do not run this with maxIterations: 0

Return value

All methods return:

  events: [],
  occurrences: [],


Root ICAL.Component from parsed data.


  • RECURRENCE-ID: check that within same day?
  • Get data from moment-timezone instead?

See also: