Ical Calendar Feed Parsing
Pricing
$0.30 / 1,000 normalized event instances
Ical Calendar Feed Parsing
Teams importing calendars from multiple providers need to validate, normalize, expand, deduplicate, and consolidate ICS data without silently losing events, shifting times, or overlooking scheduling conflicts.
Pricing
$0.30 / 1,000 normalized event instances
Rating
0.0
(0)
Developer
Jordan Nabbe
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
ICS Batch Doctor - iCalendar Feed Parser, Validator and Normalizer
ICS Batch Doctor is for automation teams, calendar-integration developers, operations coordinators and SaaS vendors who have to import calendars exported from many different providers. Give it real .ics files (as text or base64), ZIP archives full of .ics files, or explicit HTTPS links. It gives back flat event-instance rows you can import right away: recurring meetings are expanded inside your date window, times are converted to UTC and to your target time zone, the original local values and TZID are kept for auditing, and every row names its source file. Validation findings, duplicates across calendars, scheduling conflicts and per-file failures go into a run summary, so one broken file never costs you the rows from the good ones.
What it does
- Parses the actual calendar bytes. It handles RFC 5545 line unfolding, property parameters (quoted values included), text unescaping, nested components (alarms are skipped) and strict UTF-8 decoding. Base64 input is validated strictly.
- Reads ZIP batches in memory. Stored and DEFLATE entries are supported, and every entry is checked against its CRC. Path traversal, encrypted entries, ZIP64, split archives, unsupported compression and expansion ratios above 200:1 are rejected one entry at a time. The other entries keep processing.
- Expands recurrence inside your window. Supported: DAILY, WEEKLY, MONTHLY and YEARLY rules with INTERVAL, COUNT, UNTIL, BYDAY (including ordinals such as -1SU), BYMONTHDAY, BYMONTH and WKST. EXDATE, RDATE and RECURRENCE-ID overrides (moved or cancelled) are applied before duplicate and conflict analysis. When a month has no such date (for example the 31st), that month is skipped rather than rolled over.
- Handles time zones honestly. IANA zones come from Node's built-in time-zone database. Embedded VTIMEZONE STANDARD/DAYLIGHT rules are interpreted. An unknown TZID produces an error finding and is never guessed. iCalendar dates use the fixed YYYYMMDD form, so there is no day/month ambiguity to resolve.
- Stores one clean row per event instance in the dataset. Each row has stable eventId and canonicalId, UID, summary, description, location, allDay, original start/end and TZID, startUtc/endUtc, startTarget/endTarget with UTC offset, recurrence identity, RRULE, status, transparency, organizer, attendees (as a JSON string), URL, created/modified timestamps and sequence.
- Writes a run summary to the OUTPUT record. It holds the outcome block (status, items received/succeeded/failed, billable count, limit flag, undelivered rows), the delivered rows, and issues with severity, code, property, line number and suggested repair. It also lists duplicate groups, conflicts and per-source failures with the stage where each failed. Duplicate groups and conflicts reference emitted event IDs, so you can join them without parsing messages.
How to use it
- Click Start with the prefilled input. It runs fully offline on an inline sample calendar.
- Or paste your own input:
{"sources": [{"sourceId": "team","fileName": "team.ics","mediaType": "text/calendar","encoding": "utf8","content": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nBEGIN:VEVENT\r\nUID:planning@example.com\r\nDTSTART:20261005T090000Z\r\nDTEND:20261005T100000Z\r\nRRULE:FREQ=WEEKLY;COUNT=2\r\nSUMMARY:Operations planning\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"}],"dateWindow": { "start": "2026-10-01T00:00:00Z", "end": "2026-11-01T00:00:00Z" },"targetTimeZone": "Europe/London","deduplication": { "enabled": true, "mode": "uid_or_fingerprint" },"conflictDetection": { "enabled": true, "includeTransparentEvents": false },"limits": { "maxInstances": 1000, "maxSourceBytes": 10485760 }}
For original file bytes, set encoding to base64. For ZIP archives, also use mediaType: application/zip and a .zip file name. Source IDs must be unique. The window includes its start and excludes its end. Events are selected when they overlap the window and are never clipped.
Output example
| sourceRef | summary | allDay | startUtc | startTarget | recurring |
|---|---|---|---|---|---|
| operations-calendar:operations.ics | Maintenance review | false | 2026-10-05T09:00:00Z | 2026-10-05T10:00:00+01:00 | true |
| operations-calendar:operations.ics | Maintenance review | false | 2026-10-12T09:00:00Z | 2026-10-12T10:00:00+01:00 | true |
| operations-calendar:operations.ics | Team offsite | true | 2026-10-19T23:00:00Z | 2026-10-20T00:00:00+01:00 | false |
OUTPUT.outcome.status takes one of these values:
SUCCEEDEDPARTIAL: some sources or events failed.LIMIT_REACHED: your spending limit or yourmaxInstancescap stopped delivery.undeliveredcounts the occurrences withheld, and anINSTANCE_LIMITissue reportsskippedInstanceswhen the cap was the cause.FAILED
If no usable event row was produced, the summary is still written, and then the run exits with an error so a failure is never reported as a success. Invalid input is rejected before any processing.
Pricing
Pay per event. The main charge is per event instance; everything else listed below is free.
| Event | Price (USD) | Charged when |
|---|---|---|
Normalized event instance (apify-default-dataset-item) | $0.0003 | A normalized event instance row is stored in the default dataset. |
Not charged:
- issue findings
- duplicate groups
- conflicts
- source failures
- OUTPUT summary
Examples (default 512 MB memory, one start event):
- One weekly meeting expanded to two instances: 2 × $0.0003 = $0.0006
- Batch of 1000 event instances: 1000 × $0.0003 = $0.30
Spending limit: when you set a maximum cost per run, the Actor stops before it would exceed it, keeps every event instance it already delivered and reports limitReached in the OUTPUT record. It never delivers results beyond the limit.
The Store's Pricing tab shows the prices in force. If this section and the Pricing tab ever differ, the Pricing tab applies.
Use cases
- Validate batches of calendar exports before a migration or bulk import.
- Build UTC event tables for reporting, BI or synchronization jobs.
- Audit time-zone conversions while keeping the original local time and TZID.
- Find the same meeting imported from several calendars.
- Flag overlapping events for operations planning.
- Feed structured calendar rows and machine-readable findings into databases, spreadsheets or LLM pipelines.
Limits
- Scope. This is a bounded calendar importer, not a complete RFC 5545 scheduling engine.
- Dates and windows. Dates must fall between 1900 and 2100. Windows can be up to 3660 days.
- Run size. Each run accepts up to 100 sources, up to 1000 calendar documents and up to 1000 entries per archive. It emits at most
maxInstancesevent instances (up to 10000). Occurrences beyond the cap are counted, not emitted, and the run reportsLIMIT_REACHED. - Data size. Each source defaults to 10 MiB (maximum 25 MiB). Total decoded data is capped at 32 MiB.
- Time and work. Processing has a four-minute deadline and a work budget. Very long recurrence histories may need a narrower window.
- Reporting. Hitting any limit produces an explicit finding, source failure or outcome status.
Not supported:
- Sub-daily frequencies, BYSETPOS, BYWEEKNO, BYYEARDAY and BYHOUR/BYMINUTE/BYSECOND.
- Period-valued RDATE and RECURRENCE-ID RANGE.
- Alarms, attachments and free/busy data.
If a file repeats the master event for the same UID, that UID is reported as an issue. The Actor does not silently pick one revision.
Remote sources are limited to the exact HTTPS hosts apify.com, github.com and stackoverflow.com. Redirects to any other host are rejected, and that includes GitHub's raw-content host. For Google, Microsoft, iCloud, Proton or private feeds, download the file and pass it inline. Downloads resolve to public IPv4 addresses only, check every redirect (at most five), require a calendar, ZIP or plain-text content type and time out after 30 seconds. Links inside calendars are never fetched.
FAQ
Does it really parse files, or only pre-extracted text? It parses real ICS bytes and reads ZIP archives through their central directory, inflating entries with Node zlib.
Does the sample need internet access? No. The prefilled input is inline. No network request is made for inline sources.
Will one invalid file stop the run? No. A bad event produces an issue, and a bad source or archive entry produces a source failure. All other sources continue.
What happens when my calendar has more occurrences than maxInstances?
The first occurrences up to the cap are delivered, the rest are counted in undelivered, and the status is LIMIT_REACHED. Raise the cap or narrow the window to get the rest.
Are duplicates removed? No. Duplicate groups identify matching occurrences across different sources. Every event row is kept, so you decide which one wins.
Does it produce quality or confidence scores? No. It reports only measured facts: counts, codes, line numbers and properties.
Is my calendar content private? Calendar content is never written to logs and no temporary files are created. Event fields do appear in your results, so keep the run's storage private when you process confidential calendars. Remote URLs are not copied into results.