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, built on a fully type-safe Pydantic v2 data model.
| Feature | ical |
icalendar |
ics.py |
|---|---|---|---|
| Built-in recurrence expansion | ✅ | ❌ needs recurring-ical-events |
❌ |
| Pydantic v2 data model | ✅ | ❌ | ❌ |
Full type annotations (py.typed) |
✅ strict mypy | ✅ 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
pip install ical
Requires Python 3.11+.
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)
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.
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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ical-14.0.0.tar.gz.
File metadata
- Download URL: ical-14.0.0.tar.gz
- Upload date:
- Size: 148.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
675c619fff68af665894932f79cec5b53f43afce36a2379d945916f7adb3fca4
|
|
| MD5 |
ee77aeac6926f62d04e6a2fb5ff3110a
|
|
| BLAKE2b-256 |
845b0c0dfbf68b7f698243535e692f999b94f99d2cd3bfb8bc4bcac7d6513c0b
|
Provenance
The following attestation bundles were made for ical-14.0.0.tar.gz:
Publisher:
publish.yaml on allenporter/ical
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ical-14.0.0.tar.gz -
Subject digest:
675c619fff68af665894932f79cec5b53f43afce36a2379d945916f7adb3fca4 - Sigstore transparency entry: 2122000331
- Sigstore integration time:
-
Permalink:
allenporter/ical@ee5884357d2220c8025325c72867a113f0620920 -
Branch / Tag:
refs/tags/14.0.0 - Owner: https://github.com/allenporter
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@ee5884357d2220c8025325c72867a113f0620920 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ical-14.0.0-py3-none-any.whl.
File metadata
- Download URL: ical-14.0.0-py3-none-any.whl
- Upload date:
- Size: 139.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ae5eeb82e044da1a9bb689225bccaefc4ba9eaa049adfa4f3b5179a67f81f105
|
|
| MD5 |
311a9c115f69154401a9b4d25f490213
|
|
| BLAKE2b-256 |
96b2a6281456a263a95929453da11b9b135ac0c49f91fd67dd6435a1f5d250b2
|
Provenance
The following attestation bundles were made for ical-14.0.0-py3-none-any.whl:
Publisher:
publish.yaml on allenporter/ical
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ical-14.0.0-py3-none-any.whl -
Subject digest:
ae5eeb82e044da1a9bb689225bccaefc4ba9eaa049adfa4f3b5179a67f81f105 - Sigstore transparency entry: 2122000419
- Sigstore integration time:
-
Permalink:
allenporter/ical@ee5884357d2220c8025325c72867a113f0620920 -
Branch / Tag:
refs/tags/14.0.0 - Owner: https://github.com/allenporter
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@ee5884357d2220c8025325c72867a113f0620920 -
Trigger Event:
release
-
Statement type: