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) or20250415T090000(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,-1for 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 occurrencesUNTIL— 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 nameX-WR-TIMEZONE— Google's default timezone for the calendarX-APPLE-STRUCTURED-LOCATION— Apple's rich location dataX-MICROSOFT-CDO-IMPORTANCE— Outlook's priority field
Common Pitfalls
Real-world ICS files routinely deviate from the spec. Common issues include:
- Missing
UIDon 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 →