A modern Python RFC 5545 iCalendar library with built-in recurring event support — no companion libraries needed.
from ical.calendar_stream import IcsCalendarStream cal = IcsCalendarStream.calendar_from_ics(ics_content) # Recurring events are automatically expanded — just iterate for event in cal.timeline: print(event.start, event.summary)
Why ical?
Most Python iCalendar libraries require two separate packages to handle recurring events:
# The typical ecosystem approach — two libraries, two APIs import icalendar import recurring_ical_events cal = icalendar.Calendar.from_ical(ics_content) events = recurring_ical_events.of(cal).between(start, end)
ical handles this natively with a single, unified Timeline interface, with a Pythonic API and validated inputs.
| Feature | ical |
icalendar |
ics.py |
|---|---|---|---|
| Built-in recurrence expansion | ✅ | ❌ needs recurring-ical-events |
❌ |
Pythonic attribute access (event.start, event.summary) |
✅ | ❌ (event.get('DTSTART').dt) |
✅ |
| Input validation with clear error messages | ✅ | ❌ | ❌ |
Full type annotations (py.typed) |
✅ strict ty |
✅ v7+ | ❌ |
| Application-level store API | ✅ | ❌ | ❌ |
| RFC 7986 / RFC 6868 / RFC 8536 | ✅ | partial | ❌ |
| Active maintenance | ✅ | ✅ | ⚠️ stalled |
Used By
- Home Assistant — powers the Local Calendar, Remote Calendar, and Google Calendar integrations (including serving locally-synced calendars for performance)
Installation
uv add ical
Or with pip:
pip install ical
Requires Python 3.11+.
Fetching calendars from a url asynchronously (see below) requires the optional async extra, which pulls in aiohttp:
pip install ical[async]
Quickstart
Reading an .ics file
Parse a calendar file and iterate over events in chronological order, with recurring events automatically expanded:
from pathlib import Path from ical.calendar_stream import IcsCalendarStream from ical.exceptions import CalendarParseError filename = Path("calendar.ics") with filename.open() as ics_file: try: cal = IcsCalendarStream.calendar_from_ics(ics_file.read()) except CalendarParseError as err: print(f"Failed to parse '{filename}': {err}") else: for event in cal.timeline: print(event.start, event.summary)
Fetching a remote .ics url
Fetching a remote calendar over HTTP without blocking the event loop requires the optional ical[async] extra:
from ical.calendar_stream import IcsCalendarStream cal = await IcsCalendarStream.calendar_from_url("https://example.com/calendar.ics") for event in cal.timeline: print(event.start, event.summary)
An existing aiohttp.ClientSession can be passed in via the session argument to reuse connection pooling; otherwise a session is created and closed automatically for the request.
Creating a calendar
from datetime import date from ical.calendar import Calendar from ical.event import Event cal = Calendar() cal.events.append( Event(summary="Team standup", start=date(2024, 1, 15), end=date(2024, 1, 16)), ) for event in cal.timeline: print(event.summary)
Writing an .ics file
from pathlib import Path from ical.calendar_stream import IcsCalendarStream with Path("output.ics").open("w") as f: f.write(IcsCalendarStream.calendar_to_ics(cal))
Recurring events
Recurring events are stored once in the calendar but automatically expanded by the Timeline:
from datetime import date from ical.calendar import Calendar from ical.event import Event from ical.types.recur import Recur cal = Calendar() cal.events.append( Event( summary="Weekly standup", start=date(2024, 1, 15), end=date(2024, 1, 16), rrule=Recur.from_rrule("FREQ=WEEKLY;COUNT=10"), ) ) # All 10 occurrences are expanded automatically for event in cal.timeline: print(event.start, event.summary)
Application-level API
For managing calendar state in an application (ensuring timezones are set correctly, editing individual instances of recurring events, etc.), use ical.store:
from ical.store import EventStore from ical.event import Event from datetime import datetime, timezone store = EventStore() store.add(Event(summary="Meeting", start=datetime(2024, 1, 15, 9, tzinfo=timezone.utc)))
See the full documentation for the complete API reference.
Technical Guides
We publish detailed technical guides exploring the library's design:
Supported RFCs
- RFC 5545 — Internet Calendaring and Scheduling Core Object Specification (iCalendar)
- RFC 6868 — Parameter Value Encoding in iCalendar and vCard
- RFC 7986 — New Properties for iCalendar
- RFC 8536 — The Time Zone Information Format (TZif)
Comparison with other libraries
ical is designed for applications that need a complete, modern solution. You may prefer an alternative if:
icalendar— You need low-level control over raw iCalendar components, jCal (JSON) support, or maximum ecosystem compatibility. You prefer to expand recurring events manually and don't mind a second library (recurring-ical-events). You need to support legacy Python versions (3.8+) thaticaldoes not target.ics.py— You need a simple read-only script and do not require recurrence support. Note: no stable release since 0.7.2 (August 2021).
Contributing
See CONTRIBUTING.md for setup and development instructions.