How to Compare Two ICS Files for Real Calendar Changes

CalendarLint EditorialPublished 4 min read

Compare two ICS files by event identity, scheduled fields, recurrence exceptions, and source differences without confusing export formatting with calendar changes.

On this page

To compare two ICS files, match events by their identities before reviewing dates and other fields. Then inspect the raw source differences. A text diff alone can exaggerate reformatting, while a calendar preview can hide changes to attendees, recurrence rules, or metadata.

Use three questions: Is it the same event? What changed in its data? Does that change the schedule or workflow? Keep uncertain matches separate from confirmed edits.

Key Takeaways

  • Match recurring exceptions with both UID and RECURRENCE-ID.
  • Review added, removed, and changed records separately.
  • Timestamp and formatting changes deserve inspection even when meeting times are unchanged.
  • An event missing from a later export is not proof that a cancellation was sent.

Establish which export is the baseline

Name the files clearly, such as before.ics and after.ics. Record the source account, calendar, export date, and date-range settings.

If the first export covers a full year and the second covers one month, “removed” can mean “outside the export range.” Likewise, an export made with reduced detail can omit descriptions or guests without anyone editing the live events.

Keep both originals. Compare copies so saving a normalized version does not overwrite the evidence.

Use a controlled pair before a private calendar

This synthetic baseline contains three independent events. Save it with UTF-8 encoding and CRLF line endings.

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CalendarLint//Comparison Example//EN
BEGIN:VEVENT
UID:review@example.test
DTSTAMP:20261105T000000Z
DTSTART:20261110T090000Z
DTEND:20261110T093000Z
SUMMARY:Design review
END:VEVENT
BEGIN:VEVENT
UID:archive@example.test
DTSTAMP:20261105T000000Z
DTSTART:20261111T140000Z
DTEND:20261111T143000Z
SUMMARY:Archive check
END:VEVENT
BEGIN:VEVENT
UID:notes@example.test
DTSTAMP:20261105T000000Z
DTSTART:20261112T100000Z
DTEND:20261112T103000Z
SUMMARY:Planning notes
DESCRIPTION:Bring the draft agenda.
END:VEVENT
END:VCALENDAR

Create after.ics from a copy with these exact edits:

  1. Keep review@example.test, but move its start to 20261110T110000Z and end to 20261110T113000Z.
  2. Remove the entire archive@example.test event.
  3. Keep the notes event's fields, but move its DESCRIPTION line above SUMMARY.
  4. Add the following component before END:VCALENDAR:
BEGIN:VEVENT
UID:handoff@example.test
DTSTAMP:20261105T000000Z
DTSTART:20261113T150000Z
DTEND:20261113T153000Z
SUMMARY:Project handoff
END:VEVENT

These instructions fully define the second input without hiding changes inside an unrelated sample download.

Check the expected differences

The controlled pair should produce this human interpretation:

| Identity | Expected change | Meaning | | --- | --- | --- | | review@example.test | Start and end move two hours later | Same event, changed schedule | | archive@example.test | Record absent from the second file | Removed from this export | | notes@example.test | Property order changes | No intended change to event values | | handoff@example.test | New record in the second file | Added to this export |

The property reorder is useful because it makes the raw diff visibly different without changing the notes event's values. It is one controlled case, not evidence that a comparison tool recognizes every form of semantic equivalence.

Compare the files in CalendarLint

Open the baseline, then use ICS Files and its version controls. For a separate publisher export, choose Compare a new ICS file. For a repair saved inside the project, compare the saved version against the original.

The comparison provides All, Events, Issues, and Source views, as described in the version comparison workflow. Use Events to inspect the changed records and Source to verify the actual lines.

For the controlled pair, check that the design review retains its identity, the handoff is added, the archive check is removed, and the property reorder has no intended schedule effect. Review the source even when the event summary looks unsurprising.

The current comparison implementation groups records using UID and recurrence identity. This supports ordinary retained-identity edits; it does not establish the publisher's intent when identities change.

Handle recurrence and ambiguous identities explicitly

A moved occurrence belongs to its original recurrence slot. RECURRENCE-ID identifies that slot; its new DTSTART can be different. Matching only the new date can misclassify a move as a deletion and addition (RFC 5545, recurrence identity).

If the exporter generates new UIDs, the two files may show an old record removed and a new one added. Similar titles and times can suggest a relationship, but they are not conclusive evidence. Ask the publisher whether identity was regenerated.

Also check:

  • Duplicate records with the same UID and recurrence identity.
  • RRULE, RDATE, and EXDATE changes that affect dates beyond the current preview.
  • VTIMEZONE changes that can alter interpretation without changing a local clock value.
  • Organizer, attendee, status, and alarm changes that affect behavior without moving the event.

A finite recurrence preview cannot prove equivalence for an unbounded series. Compare the rules and relevant future boundaries as well.

Decide what to keep before exporting

Treat DTSTAMP, SEQUENCE, and LAST-MODIFIED as metadata to interpret, not automatically discard. A metadata-only change may leave the visible schedule unchanged while still mattering to a meeting update process.

Write down the expected changes before accepting a repaired version. Then check identities, dates, recurrence exceptions, and unrelated fields against that list. Download the verified version with a distinct filename and keep the originals.

Verification scope: the controlled pair was parsed and checked locally with ical.js 2.2.1 on November 5, 2026. CalendarLint comparison behavior was checked against the current source and its existing comparison tests. No universal equivalence detection or cross-client import result is claimed.