ical
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.
Release files for ical 14.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ical-14.1.0.tar.gz | 156.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ical-14.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 300.4 kB
Release files / ical-14.1.0.tar.gz
| Download URL | ical-14.1.0.tar.gz |
|---|---|
| Size | 156.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
768274c90a8882ef8cd6610688c161cfe44d22c2769b5d4833efeb27910b7976
|
|
BLAKE2b-256 checksum How to use checksums |
e437c20ea9cb032360b476207f23544b4c113fae4f79619951b3b1c1dfdcec38
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.
Transparency logRelease files / ical-14.1.0-py3-none-any.whl
| Download URL | ical-14.1.0-py3-none-any.whl |
|---|---|
| Size | 144.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bf5ddfa278cdec8608d1bc1bd1f97b4481f7c8e5c608b3823cbaa3571bd4c238
|
|
BLAKE2b-256 checksum How to use checksums |
006dfeb3315521c7b119ce40b248a02595da3db7fc73119be45aa627642e0c6b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.
Transparency log