ics.py Documentation

repository·main·Indexed 20 days ago

https://github.com/ics-py/ics-py

A Pythonic iCalendar (RFC 5545) parser and serializer for reading and writing .ics data. The library provides a developer-friendly interface for interacting with calendar data used by Google, Apple, and Android, featuring support for Calendar, Event, and Todo objects. It includes utilities for handling custom properties via the .extra attribute, managing event timespans, and comparing/ordering events using Timespan.cmp_tuple(). Note that version 0.8.0.dev0 does not currently support RRULE recurrence rules.

Tokens
15.6K
Snippets
55
Records
85
Agent score
71%

What's inside ics.py

  1. Get started with ics.py

    main

    ics.py is a Pythonic iCalendar library designed to read and write .ics data following the RFC5545 specification. It is intended for developers who want a user-friendly way to handle iCalendar data without manually managing the complexities of the specification.

    Requirements:

    • Python 3.6 or higher

    To begin using the library, you should follow the installation and quickstart guides to set up your environment and create your first calendar.

  2. Missing support for recurrent events (RRULE)

    main

    Currently, ics.py does not support the RRULE property. This means:

    • It cannot parse the RRULE property from input files.
    • It cannot use recurrence rules within the ics.timeline.Timeline class.

    Support for RRULE is planned for a future version (expected before version 1.0).

  3. Order Events and Todos using comparison operators

    main

    You can sort lists of Event or Todo objects using standard comparison operators (<, >, <=, >=) or the sort() method. The ordering logic follows a specific priority of attributes:

    Event Ordering Priority:

    1. begin time
    2. end time (or effective end time if duration is used)
    3. summary

    Todo Ordering Priority:

    1. due time
    2. begin time
    3. summary

    Key behaviors:

    • Effective Time: For EventTimespan, the comparison uses the effective end time, whether it was defined via end_time or duration.
    • Missing Values: Attributes that are not set (None) are treated as datetime.min for times or an empty string "" for summaries. Consequently, objects with unset attributes will always be sorted before objects where those attributes are set.
    • Timezones: Naive datetime objects are compared in local time.
    import ics
    from datetime import datetime, timedelta as td
    
    # Events are ordered by begin, end, then summary
    e1 = ics.Event(summary="A")
    e2 = ics.Event(summary="B")
    print(e1 < e2)  # True
    
    # Todos are ordered by due, begin, then summary
    t1 = ics.Todo(begin=datetime(2023, 1, 1))
    t2 = ics.Todo(due=datetime(2023, 1, 2))
    print(t1 < t2)  # True
  4. Understand the difference between Equality and Ordering

    main

    In ics.py, equality (==) and ordering (<, >) use different logic and different sets of attributes:

    1. Equality (==, !=): Uses all public attributes of the class. It is strict and requires the objects to be of the same class.
    2. Ordering (<, >, etc.): Uses a subset of attributes via a cmp_tuple(). For an Event, this is (begin, end, summary).

    Warning: Because ordering only looks at a subset of attributes, the ordering is not total. For example, (x <= y and not x < y) does not necessarily imply x == y, because x and y might differ in attributes that are ignored by the ordering logic (like uid or location).

  5. Handle unknown or custom iCalendar properties using .extra

    main

    Because ics.py does not support the full RFC 5545 specification, any properties or containers encountered during parsing that are not explicitly handled by the library are stored in the .extra attribute.

    All major objects—ics.Calendar, ics.Event, and ics.Todo—inherit from ics.parse.Container and therefore possess this attribute.

    • During Parsing: Unknown properties are automatically captured in .extra as either ics.parse.ContentLine objects (for simple key-value pairs) or ics.parse.Container objects (for nested components).
    • During Serialization: Any objects added to the .extra list will be included in the generated iCalendar output.
    # Example of how unknown properties appear after parsing
    # Input iCalendar snippet:
    # BEGIN:VEVENT
    #   SUMMARY:Name of the event
    #   FOO:BAR
    # END:VEVENT
    
    e.name == "Name of the event"
    e.extra == [ContentLine(name="FOO", value="BAR")]
  6. Handling unsupported properties in ics.py

    main

    ics.py does not support the full RFC 5545 specification and may not recognize all custom or extension properties used by various iCalendar creators.

    However, ics.py can still read RFC-compliant files containing unknown properties and can output files with specific properties even if it does not understand their meaning. To work with unsupported properties, you should use the low-level API as described in the Advanced guide.

  7. Compare Events and Todos for equality

    main

    You can use standard Python equality operators (== and !=) to compare Event and Todo objects.

    Important Caveats:

    • All attributes matter: Equality is based on all public attributes. This includes automatically generated fields like uid, created, last_modified, and dtstamp. Because uid is random and dtstamp defaults to datetime.now(), two events created sequentially will not be equal even if all other properties are identical.
    • Order matters for lists: For attributes that are lists (like alarms or attendees), the order of elements affects equality.
    • Extra properties: The extra container (used for custom ContentLine objects) is included in equality checks.

    To find the exact differences between two objects, use str(e) for a string representation, e.serialize() for the iCalendar format, or attr.asdict(e) to get a dictionary of all attributes.

    import ics
    from datetime import datetime
    
    e1, e2 = ics.Event(), ics.Event()
    e1 == e2  # False (due to different UIDs and dtstamps)
    
    e1.uid = e2.uid = "event1"
    e1.dtstamp = e2.dtstamp = datetime.now()
    e1 == e2  # True
    
    # To inspect differences:
    import attr
    print(attr.asdict(e1))
  8. Comparing datetimes after deserialization

    main

    When you deserialize an .ics file, the resulting tzinfo objects might not be identical to the original Python tzinfo objects (e.g., a dateutil object vs. an ics.py Timezone object). However, the datetime objects themselves will still compare as equal if they represent the same instant in UTC.

    # e1 is original, e2 is deserialized from e1's serialization
    e1_begin = e.begin
    e2_begin = e2.begin
    
    # The tzinfo objects are different
    e1_begin.tzinfo == e2_begin.tzinfo  # False
    
    # The datetimes represent the same instant
    e1_begin == e2_begin  # True
    e2 = Calendar(Calendar(events=[e]).serialize()).events[0]
    
    # the tzinfo objects are different
    e.begin.tzinfo == e2.begin.tzinfo
    # False
    
    # but the datetimes still compare equal
    e.begin == e2.begin
    # True
  9. How ics.py handles timezones

    main

    When you pass Python datetime objects to ics.py, it preserves the timezone information.

    • Naïve datetimes: If you pass a datetime with tzinfo=None, the event is considered "floating" (interpreted as local time).
    • Aware datetimes: If you pass a datetime with a timezone (e.g., from dateutil.tz or pytz), ics.py embeds a full VTIMEZONE specification in the serialized .ics file. This ensures the timezone can be reconstructed exactly on any system, even without an IANA database.
    • DTSTAMP: Every event includes a dtstamp field representing when the ics representation was created. ics.py ensures this is always an UTC timestamp per RFC standards.

    To check if a datetime is in UTC, use the is_utc() function instead of direct equality comparison, as it correctly handles various library implementations (like datetime, dateutil, and pytz).

    from datetime import datetime as dt
    from ics import Event
    from ics.timezone import is_utc
    
    t = dt(2020, 4, 1, 18, 0)
    e = Event(begin=t, uid="test", dtstamp=t)
    
    # Checking for UTC
    print(is_utc(e.dtstamp))
  10. Quickstart: Create and save an iCalendar file

    main

    To create a new calendar, import Calendar and Event from ics. You can define event properties like summary, description, begin, and end using standard Python datetime objects. Add the event to the calendar's events list and use the serialize() method to generate the iCalendar string for writing to a file.

    from datetime import datetime
    from ics import Calendar, Event
    
    c = Calendar()
    e = Event()
    e.summary = "My cool event"
    e.description = "A meaningful description"
    e.begin = datetime.fromisoformat("2022-06-06T12:05:23+02:00")
    e.end = datetime.fromisoformat("2022-06-06T13:05:23+02:00")
    c.events.append(e)
    
    # Serialize and write to a file
    with open("my.ics", "w", newline='') as f:
        f.write(c.serialize())
  11. Create a new Calendar and add Events

    main

    A Calendar object represents a unique RFC 5545 iCalendar and can contain Event, Todo, and Timeline iterators. Time and date are handled using standard Python datetime objects. You can add events to a calendar by appending an Event instance to the c.events list.

    from datetime import datetime, timezone, timedelta
    from ics import Calendar, Event
    
    c = Calendar()
    e = Event()
    e.summary = "My cool event"
    e.description = "A meaningful description"
    e.begin = datetime.fromisoformat("2022-06-06T12:05:23+02:00")
    e.end = datetime(
        year=2022,
        month=6,
        day=6,
        hour=12,
        minute=5,
        second=23,
        tzinfo=timezone(timedelta(seconds=7200)),
    )
    c.events.append(e)
  12. Install Thunderbird on Linux

    main

    Thunderbird is available for most Linux distributions via system package managers. You will need root/administrator privileges to install it.

    Use the command corresponding to your distribution:

    # Debian/Ubuntu
    sudo apt-get update
    sudo apt-get install thunderbird
    
    # Fedora
    dnf install thunderbird
    
    # openSUSE
    sudo zypper install thunderbird