Blog/RFC 5545 Explained: Inside the iCalendar Specification
·7 min read

RFC 5545 Explained: Inside the iCalendar Specification

A developer-friendly breakdown of RFC 5545, the standard that defines the iCalendar format used by every .ics file.

If you've ever worked with calendar data programmatically, you've encountered RFC 5545 — whether you knew it or not. Published by the IETF in September 2009, RFC 5545 defines the iCalendar format: the plain-text standard that underpins every .ics file, every meeting invite, and every "Add to Calendar" button on the web.

This post walks through the key parts of the specification with a developer's eye.

The Basic Structure

An iCalendar object is a text stream using CRLF line endings ( ). Every object starts with BEGIN:VCALENDAR and ends with END:VCALENDAR. Properties sit between BEGIN and END markers as PROPERTY:value pairs.

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Your App//EN
BEGIN:VEVENT
...
END:VEVENT
END:VCALENDAR

Two properties are required on the VCALENDAR component:

  • VERSION:2.0 — always 2.0 for RFC 5545 (version 1.0 was the older vCalendar format)
  • PRODID — identifies the software that created the file, in a vendor-specific format

Components

Inside VCALENDAR, you nest one or more components:

  • VEVENT — the workhorse; represents any calendar event
  • VTODO — a task with an optional due date and completion status
  • VJOURNAL — a journal entry (rarely used in practice)
  • VFREEBUSY — free/busy time blocks used in scheduling workflows
  • VTIMEZONE — an embedded timezone definition
  • VALARM — a reminder/alarm, nested inside VEVENT or VTODO

Property Value Types

RFC 5545 defines a rich type system for property values:

  • DATE-TIME — e.g. 20250415T090000Z (UTC) or 20250415T090000 (floating)
  • DATE — e.g. 20250415 (date-only, used for all-day events)
  • DURATION — e.g. PT1H30M (1 hour 30 minutes)
  • TEXT — free text, with backslash-escaping for commas and semicolons
  • URI — a URL
  • CAL-ADDRESS — an email-style address, e.g. mailto:alice@example.com
  • RECUR — the recurrence rule value type, used by RRULE

Line Folding

RFC 5545 limits lines to 75 octets. Long values are "folded" across multiple lines using a CRLF followed by a single whitespace character (space or tab):

DESCRIPTION:This is a very long description that exceeds seventy-five cha
 racters and must be folded across multiple lines in the output.

Parsers must unfold these lines before interpreting the value. This is a common source of bugs — many real-world ICS files are generated by software that folds at character boundaries rather than octet boundaries, which can corrupt multi-byte UTF-8 characters.

The RRULE Recurrence System

Recurrence rules are one of the most complex parts of RFC 5545. An RRULE value is a semicolon-separated list of rule parts:

RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR;UNTIL=20251231T235959Z

The FREQ part (required) sets the base frequency: SECONDLY, MINUTELY, HOURLY, DAILY, WEEKLY, MONTHLY, or YEARLY.

Additional rule parts narrow down which occurrences match:

  • BYDAY — day of week (MO, TU, WE, TH, FR, SA, SU), with optional ordinal prefix (2MO = second Monday)
  • BYMONTHDAY — specific days of the month (e.g. 1,-1 for first and last day)
  • BYMONTH — specific months (1–12)
  • BYSETPOS — filters the set of occurrences (e.g. -1 = last occurrence in the set)
  • COUNT — total number of occurrences
  • UNTIL — end date/time (inclusive)
  • INTERVAL — step size (e.g. FREQ=WEEKLY;INTERVAL=2 = every two weeks)
  • WKST — week start day (default: MO)

Properly expanding recurrence rules — especially with EXDATE (exception dates) and RDATE (additional dates) — is non-trivial. Libraries like python-recurring-ical-events and ical.js handle this correctly, including edge cases like DST transitions.

Timezones and VTIMEZONE

Floating date-times (no Z suffix, no TZID) are interpreted in the local timezone of whoever reads the file — which is often wrong in practice. The correct approach is to either use UTC (Z suffix) or include a TZID parameter with a timezone identifier:

DTSTART;TZID=America/New_York:20250415T090000

For maximum portability, the file should also include a VTIMEZONE component that embeds the full timezone definition, so the reader doesn't need to look it up externally:

BEGIN:VTIMEZONE
TZID:America/New_York
BEGIN:STANDARD
DTSTART:19671029T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZNAME:EST
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19870405T020000
RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
TZNAME:EDT
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
END:DAYLIGHT
END:VTIMEZONE

Extensions and X-Properties

RFC 5545 is designed for extensibility. Any property starting with X- is a vendor extension and must be silently ignored by compliant parsers that don't understand it. This is why you'll see properties like:

  • X-WR-CALNAME — Google's extension for the calendar display name
  • X-WR-TIMEZONE — Google's default timezone for the calendar
  • X-APPLE-STRUCTURED-LOCATION — Apple's rich location data
  • X-MICROSOFT-CDO-IMPORTANCE — Outlook's priority field

Common Pitfalls

Real-world ICS files routinely deviate from the spec. Common issues include:

  • Missing UID on VEVENT (required by the spec)
  • CRLF vs LF line endings (some parsers are strict about this)
  • Unescaped commas and semicolons in TEXT values
  • Incorrect line folding at byte boundaries instead of character boundaries
  • DTEND before DTSTART (rare but it happens)
  • Recurrence rules that generate zero occurrences
  • Timezone IDs that don't match IANA timezone names

A robust ICS parser needs to handle all of these gracefully. The ical.js library used by this tool is battle-tested against a wide variety of real-world files and handles most edge cases correctly.

Further Reading

Want to preview an ICS file right now?

Open ICS Viewer →