{"id":16526283,"url":"https://github.com/jens-maus/node-ical","last_synced_at":"2026-02-22T17:25:37.770Z","repository":{"id":17590778,"uuid":"264739770","full_name":"jens-maus/node-ical","owner":"jens-maus","description":"Feature-rich iCalendar/ICS (RFC 5545) parser for Node.js","archived":false,"fork":false,"pushed_at":"2026-02-15T11:57:37.000Z","size":1894,"stargazers_count":152,"open_issues_count":8,"forks_count":60,"subscribers_count":4,"default_branch":"master","last_synced_at":"2026-02-15T13:39:17.105Z","etag":null,"topics":["ical","icalendar","ics","nodejs","npm"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"peterbraden/ical.js","license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jens-maus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"jens-maus","custom":"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick\u0026hosted_button_id=RAQSDY9YNZVCL"}},"created_at":"2020-05-17T19:19:27.000Z","updated_at":"2026-02-15T11:57:41.000Z","dependencies_parsed_at":"2026-01-01T13:02:11.946Z","dependency_job_id":null,"html_url":"https://github.com/jens-maus/node-ical","commit_stats":{"total_commits":500,"total_committers":68,"mean_commits":7.352941176470588,"dds":0.75,"last_synced_commit":"1376c8a5a7f996ac8ff3b3b993e1929ea2882165"},"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"purl":"pkg:github/jens-maus/node-ical","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jens-maus%2Fnode-ical","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jens-maus%2Fnode-ical/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jens-maus%2Fnode-ical/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jens-maus%2Fnode-ical/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jens-maus","download_url":"https://codeload.github.com/jens-maus/node-ical/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jens-maus%2Fnode-ical/sbom","scorecard":{"id":515057,"data":{"date":"2025-08-11","repo":{"name":"github.com/jens-maus/node-ical","commit":"6b693e2f6b26b8b6d5ea4fb3a07dd640977aa9c5"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.3,"checks":[{"name":"Maintained","score":1,"reason":"2 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Security-Policy","score":4,"reason":"security policy file detected","details":["Info: security policy file detected: SECURITY.md:1","Warn: no linked content found","Info: Found disclosure, vulnerability, and/or timelines in security policy: SECURITY.md:1","Info: Found text in security policy: SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Code-Review","score":5,"reason":"Found 7/14 approved changesets -- score normalized to 5","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/codeql-analysis.yml:1","Warn: no topLevel permission defined: .github/workflows/dependabot-automerge.yml:1","Warn: no topLevel permission defined: .github/workflows/nodejs.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":2,"reason":"dependency not pinned by hash detected -- score normalized to 2","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:28: update your workflow using https://app.stepsecurity.io/secureworkflow/jens-maus/node-ical/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:36: update your workflow using https://app.stepsecurity.io/secureworkflow/jens-maus/node-ical/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:47: update your workflow using https://app.stepsecurity.io/secureworkflow/jens-maus/node-ical/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:61: update your workflow using https://app.stepsecurity.io/secureworkflow/jens-maus/node-ical/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/dependabot-automerge.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/jens-maus/node-ical/dependabot-automerge.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/dependabot-automerge.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/jens-maus/node-ical/dependabot-automerge.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodejs.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/jens-maus/node-ical/nodejs.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodejs.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/jens-maus/node-ical/nodejs.yml/master?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/nodejs.yml:29","Info:   0 out of   7 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   1 out of   2 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":9,"reason":"SAST tool detected but not run on all commits","details":["Info: SAST configuration detected: CodeQL","Warn: 20 commits out of 23 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":3,"reason":"7 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-jr5f-v2jv-69x6","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T01:35:51.748Z","repository_id":17590778,"created_at":"2025-08-20T01:35:51.749Z","updated_at":"2025-08-20T01:35:51.749Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29594242,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T18:54:29.675Z","status":"ssl_error","status_checked_at":"2026-02-18T18:50:50.517Z","response_time":162,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ical","icalendar","ics","nodejs","npm"],"created_at":"2024-10-11T17:27:05.580Z","updated_at":"2026-02-18T22:00:32.520Z","avatar_url":"https://github.com/jens-maus.png","language":"JavaScript","funding_links":["https://github.com/sponsors/jens-maus","https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick\u0026hosted_button_id=RAQSDY9YNZVCL"],"categories":["JavaScript"],"sub_categories":[],"readme":"# node-ical\n[![Build](https://github.com/jens-maus/node-ical/workflows/CI/badge.svg)](https://github.com/jens-maus/node-ical/actions)\n[![NPM version](https://img.shields.io/npm/v/node-ical.svg)](https://www.npmjs.com/package/node-ical)\n[![Downloads](https://img.shields.io/npm/dm/node-ical.svg)](https://www.npmjs.com/package/node-ical)\n[![Contributors](https://img.shields.io/github/contributors/jens-maus/node-ical.svg)](https://github.com/jens-maus/node-ical/graphs/contributors)\n[![License](https://img.shields.io/github/license/jens-maus/node-ical.svg)](https://github.com/jens-maus/node-ical/blob/master/LICENSE)\n[![Donate](https://img.shields.io/badge/donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick\u0026hosted_button_id=RAQSDY9YNZVCL)\n[![GitHub stars](https://img.shields.io/github/stars/jens-maus/node-ical.svg?style=social\u0026label=Star)](https://github.com/jens-maus/node-ical/stargazers/)\n\nA feature-rich iCalendar/ICS ([RFC 5545](https://tools.ietf.org/html/rfc5545)) parser for Node.js. Originally forked from [ical.js](https://github.com/peterbraden/ical.js) by Peter Braden, node-ical has evolved significantly to include robust recurrence rule (RRULE) expansion, timezone-aware date handling, exception dates (EXDATE), and recurrence overrides (RECURRENCE-ID). The library provides both synchronous and asynchronous APIs for parsing ICS files from strings, local files, and remote URLs – features specifically designed for Node.js environments.\n\n## Install\nnode-ical is available on npm:\n```sh\nnpm install node-ical\n```\n\n## API\nThe API has now been broken into three sections:\n - [sync](#sync)\n - [async](#async)\n - [autodetect](#autodetect)\n\n`sync` provides synchronous API functions.\nThese are easy to use but can block the event loop and are not recommended for applications that need to serve content or handle events.\n\n`async` provides proper asynchronous support for iCal parsing.\nAll functions will either return a promise for `async/await` or use a callback if one is provided.\n\n`autodetect` provides a mix of both for backwards compatibility with older node-ical applications.\n\nAll API functions are documented using JSDoc in the [node-ical.js](node-ical.js) file.\nThis allows for IDE hinting!\n\n### sync\n```javascript\n// import ical\nconst ical = require('node-ical');\n\n// use the sync function parseFile() to parse this ics file\nconst events = ical.sync.parseFile('example-calendar.ics');\n// loop through events and log them\nfor (const event of Object.values(events)) {\n    console.log(\n        'Summary: ' + event.summary +\n        '\\nDescription: ' + event.description +\n        '\\nStart Date: ' + event.start.toISOString() +\n        '\\n'\n    );\n};\n\n// or just parse some iCalendar data directly\nconst directEvents = ical.sync.parseICS(`\nBEGIN:VCALENDAR\nVERSION:2.0\nCALSCALE:GREGORIAN\nBEGIN:VEVENT\nSUMMARY:Hey look! An example event!\nDTSTART;TZID=America/New_York:20130802T103400\nDTEND;TZID=America/New_York:20130802T110400\nLOCATION:1000 Broadway Ave.\\, Brooklyn\nDESCRIPTION: Do something in NY.\nSTATUS:CONFIRMED\nUID:7014-1567468800-1567555199@peterbraden@peterbraden.co.uk\nEND:VEVENT\nEND:VCALENDAR\n`);\n// log the ids of these events\nconsole.log(Object.keys(directEvents));\n```\n\n### async\n```javascript\n// import ical\nconst ical = require('node-ical');\n\n// do stuff in an async function\n;(async () =\u003e {\n    // load and parse this file without blocking the event loop\n    const events = await ical.async.parseFile('example-calendar.ics');\n\n    // you can also use the async lib to download and parse iCal from the web\n    const webEvents = await ical.async.fromURL('http://lanyrd.com/topics/nodejs/nodejs.ics');\n    // you can pass standard fetch() options (e.g. headers, signal for timeout)\n    // Example: 5s timeout\n    const ac = new AbortController();\n    setTimeout(() =\u003e ac.abort(), 5000);\n    const headerWebEvents = await ical.async.fromURL(\n        'http://lanyrd.com/topics/nodejs/nodejs.ics',\n        { headers: { 'User-Agent': 'API-Example/1.0' }, signal: ac.signal }\n    );\n\n    // parse iCal data without blocking the main loop for extra-large events\n    const directEvents = await ical.async.parseICS(`\nBEGIN:VCALENDAR\nVERSION:2.0\nCALSCALE:GREGORIAN\nBEGIN:VEVENT\nSUMMARY:Hey look! An example event!\nDTSTART;TZID=America/New_York:20130802T103400\nDTEND;TZID=America/New_York:20130802T110400\nDESCRIPTION: Do something in NY.\nUID:7014-1567468800-1567555199@peterbraden@peterbraden.co.uk\nEND:VEVENT\nEND:VCALENDAR\n    `);\n})()\n    .catch(console.error.bind());\n\n// old fashioned callbacks cause why not\n\n// parse a file with a callback\nical.async.parseFile('example-calendar.ics', function(err, data) {\n    if (err) {\n        console.error(err);\n        process.exit(1);\n    }\n    console.log(data);\n});\n\n// or a URL\nical.async.fromURL('http://lanyrd.com/topics/nodejs/nodejs.ics', function(err, data) { console.log(data); });\n\n// or directly\nical.async.parseICS(`\nBEGIN:VCALENDAR\nVERSION:2.0\nCALSCALE:GREGORIAN\nBEGIN:VEVENT\nSUMMARY:Hey look! An example event!\nDTSTART;TZID=America/New_York:20130802T103400\nDTEND;TZID=America/New_York:20130802T110400\nDESCRIPTION: Do something in NY.\nUID:7014-1567468800-1567555199@peterbraden@peterbraden.co.uk\nEND:VEVENT\nEND:VCALENDAR\n`, function(err, data) { console.log(data); });\n```\n\nNote: When using the `ical.async.*` functions in a separate async context from your main code,\nerrors will be thrown in that separate context. Therefore, you must wrap these function calls\nin try/catch blocks to properly handle any errors. For example:\n\n```javascript\ntry {\n  const events = await ical.async.parseFile('calendar.ics');\n  // Process events\n} catch (error) {\n  console.error('Failed to parse calendar:', error);\n}\n```\n\n### autodetect\nThese are the old API examples, which still work and will be converted to the new API automatically.\nFunctions with callbacks provided will also have better performance over the older versions even if they use the old API.\n\nParses a string with ICS content in sync. This can block the event loop on big files.\n```javascript\nconst ical = require('node-ical');\nical.parseICS(str);\n```\n\nParses a string with ICS content in async to prevent the event loop from being blocked.\n```javascript\nconst ical = require('node-ical');\nical.parseICS(str, function(err, data) {\n    if (err) console.log(err);\n    console.log(data);\n});\n```\n\nParses a string with an ICS file in sync. This can block the event loop on big files.\n```javascript\nconst ical = require('node-ical');\nconst data = ical.parseFile(filename);\n```\n\nParses a string with an ICS file in async to prevent event loop from being blocked.\n```javascript\nconst ical = require('node-ical');\nconst data = ical.parseFile(filename, function(err, data) {\n    if (err) console.log(err);\n    console.log(data);\n});\n```\n\nReads in the specified iCal file from the URL, parses it and returns the parsed data.\n```javascript\nconst ical = require('node-ical');\nical.fromURL(url, options, function(err, data) {\n    if (err) console.log(err);\n    console.log(data);\n});\n```\n\nFetch the specified URL using the native fetch API (```options``` are passed to the underlying `fetch()` call) and call the function with the result (either an error or the data). Requires Node.js 18+ (or any environment that provides a global `fetch`).\n\n#### Example: Print list of upcoming node conferences\n\nSee [`examples/example.js`](./examples/example.js) for a synchronous example script.\n\n```javascript\nconst ical = require('node-ical');\nconst months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\n\nical.fromURL('http://lanyrd.com/topics/nodejs/nodejs.ics', {}, function (err, data) {\n    for (let k in data) {\n        if (data.hasOwnProperty(k)) {\n            const ev = data[k];\n            if (data[k].type == 'VEVENT') {\n                console.log(`${ev.summary} is in ${ev.location} on the ${ev.start.getDate()} of ${months[ev.start.getMonth()]} at ${ev.start.toLocaleTimeString('en-GB')}`);\n            }\n        }\n    }\n});\n```\n\n### Recurrence rule (RRULE) and Timezone Handling\n\nWhen expanding recurrences (RRULEs), node-ical takes the timezone from the DTSTART field into account:\n\n- **If a timezone is present in DTSTART**, all recurrence dates are calculated in that timezone.\n- **If no timezone is present**, recurrences are calculated in UTC. The original offset from DTSTART and the current offset of the recurrence date are considered.\n- For correct results in complex timezone scenarios, always specify the timezone explicitly in DTSTART.\n\n### Exception dates (EXDATE) and Recurrence overrides (RECURRENCE-ID)\n\nnode-ical provides RFC 5545-compliant handling of exception dates and recurrence overrides:\n\n#### EXDATE – Excluding dates from recurrence\n\nException dates are stored in an object with **dual-key access** for maximum flexibility:\n\n```javascript\nconst event = data['some-recurring-event-uid'];\n\n// Simple date-based lookup (works for all events)\nif (event.exdate?.['2024-07-15']) {\n  console.log('July 15th is excluded from this recurring event');\n}\n\n// Precise time-based lookup (for events recurring multiple times per day)\nif (event.exdate?.['2024-07-15T14:00:00.000Z']) {\n  console.log('Only the 2 PM instance on July 15th is excluded');\n}\n```\n\n**How it works:**\n- For `VALUE=DATE` (date-only): Only the date key is created (`YYYY-MM-DD`)\n- For `VALUE=DATE-TIME`: **Both** date key and full ISO timestamp key are created\n- Both keys reference the same `Date` object (no memory overhead)\n\n**Why dual keys?**\n- **Backward compatibility**: Existing code using date-only lookups continues to work\n- **RFC 5545 compliance**: Supports precise exclusion of specific instances\n- **Practical use**: Simple lookups for most cases, precise matching when needed\n\n#### RECURRENCE-ID – Modifying specific instances\n\nRecurrence overrides follow the same dual-key pattern:\n\n```javascript\n// Access override for entire day\nconst override = event.recurrences?.['2024-07-15'];\n\n// Access override for specific time instance\nconst preciseOverride = event.recurrences?.['2024-07-15T14:00:00.000Z'];\n```\n\n### Expanding recurring events\n\nFor convenience, node-ical provides `expandRecurringEvent()` to expand recurring events into individual instances with proper handling of EXDATE, RECURRENCE-ID, and DST transitions:\n\n```javascript\nconst ical = require('node-ical');\nconst events = ical.sync.parseFile('calendar.ics');\nconst event = Object.values(events).find(e =\u003e e.type === 'VEVENT' \u0026\u0026 e.rrule);\n\n// Expand recurring event for next 30 days\nconst instances = ical.expandRecurringEvent(event, {\n  from: new Date(),\n  to: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)\n});\n\n// Each instance has all the information you need\ninstances.forEach(instance =\u003e {\n  console.log(instance.summary);           // Event title\n  console.log(instance.start);             // Start date/time\n  console.log(instance.isFullDay);         // Whether it's an all-day event\n  console.log(instance.isOverride);        // Whether it's a modified instance\n});\n```\n\n**Options:**\n- `from` / `to` – Date range to expand (inclusive)\n- `includeOverrides` – Apply RECURRENCE-ID modifications (default: `true`)\n- `excludeExdates` – Exclude EXDATE dates (default: `true`)\n- `expandOngoing` – Include events starting before `from` but still ongoing (default: `false`)\n\n**Key features:**\n- DST-safe: Full-day events stay on the correct calendar day across timezone transitions\n- Proper EXDATE and RECURRENCE-ID handling built-in\n- Returns sorted array of instances with rich metadata\n- Works with both recurring and non-recurring events\n\n### Working with the parsed dates\n\n- Every parsed `start`/`end` value is a JavaScript `Date` that represents the **exact instant in UTC**. When DTSTART carries an IANA timezone, the parser attaches a non-enumerable `tz` property (for example `event.start.tz === 'Europe/Zurich'`). All-day values also expose `dateOnly === true`, which makes it easy to distinguish floating all-day events from timed ones.\n- Prior to v0.22, all-day DTSTART values were normalised to `00:00:00Z` and their timezone metadata was lost. The modern behaviour preserves the original instant *and* its timezone, which keeps RRULE expansions and DST transitions correct. Treat this as a breaking behaviour change when migrating from older releases.\n- To render the day in the originating timezone (for example, to display an all-day event on “25 March” in its local time), derive it explicitly:\n\n    ```js\n    const localDay = new Intl.DateTimeFormat('de-CH', {\n        timeZone: event.start.tz ?? 'UTC',\n        year: 'numeric',\n        month: '2-digit',\n        day: '2-digit',\n    }).format(event.start);\n    ```\n\n    If your runtime ships [Temporal](https://tc39.es/proposal-temporal/), you can also round-trip via:\n\n    ```js\n    const zoned = Temporal.ZonedDateTime.from({\n        timeZone: event.start.tz ?? 'UTC',\n        instant: Temporal.Instant.fromEpochMilliseconds(event.start.valueOf()),\n    }).startOfDay();\n    ```\n\nConsumers that previously relied on implicit midnight UTC should update their handling to restore the local day using the attached timezone.\n\nSee the following example scripts for practical demonstration:\n- [`examples/example-rrule-basic.js`](./examples/example-rrule-basic.js) – minimal RRULE expansion with native `Date`\n- [`examples/example-rrule-moment.js`](./examples/example-rrule-moment.js)\n- [`examples/example-rrule-luxon.js`](./examples/example-rrule-luxon.js)\n- [`examples/example-rrule-dayjs.js`](./examples/example-rrule-dayjs.js)\n- [`examples/example-rrule-datefns.js`](./examples/example-rrule-datefns.js)\n- [`examples/example-rrule-vanilla.js`](./examples/example-rrule-vanilla.js)\n\nEach library may display timezones differently, but the recurrence logic is the same.\n\n### TypeScript support\n\nnode-ical includes full TypeScript type definitions. See [`examples/example-typescript.ts`](./examples/example-typescript.ts) for a complete example showing type-safe access to calendar properties, including the `vcalendar` object for accessing calendar-level metadata like `WR-CALNAME`.\n\n## Under the hood\n\n**Windows/IANA time zones**: node-ical maps Windows time zone IDs and common legacy display-name labels to IANA via a generated `windowsZones.json`. It’s built from CLDR (`windowsZones.xml`) and augmented with legacy aliases for resilience; see `build/README.md` for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjens-maus%2Fnode-ical","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjens-maus%2Fnode-ical","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjens-maus%2Fnode-ical/lists"}