Python icalendar
This is a very good, very powerful tool that reads and writes icalendar files (*.ics). This module is sorely lacking documentation, it would be advisable for you read the source to get a better understanding of how it should be used (The documentation refers to the unittests, but they also need updating).
icalendar format
See icalendar format notes.
Basic Usage
Firstly, understand that icalendar files comprise primarily of calendars, events, and alarms. each of these fields can be technically nested under each other.
Reading
## Read a .ics file fd = open( 'calendar.ics', 'rb' ) data = fd.read() fd.close() cal = Calendar.from_ical(data) ## retrieve all occurrences of a particular datatype ## in calendar. ## Generally, this is not advisable, because you do not ## know which datatype it is nested under. ## (which is a problem, if for instance, you have multiple events, ## and multiple alarms under those events) events = cal.walk('VEVENT') [Event([(u'CREATED', <icalendar.prop.vDDDTypes at 0x7fd0606e5c10>), (u'LAST-MODIFIED', <icalendar.prop.vDDDTypes at 0x7fd060660410>), (u'DTSTAMP', <icalendar.prop.vDDDTypes at 0x7fd060660490>), (u'UID', vText('00ccc052-ac26-4aa3-9105-f02b2e12e497')), (u'SUMMARY', vText('Heather MacNevin Birthday (?)')), (u'RRULE', vRecur([(u'FREQ', [u'YEARLY'])])), (u'DTSTART', <icalendar.prop.vDDDTypes at 0x7fd0606606d0>), (u'DTEND', <icalendar.prop.vDDDTypes at 0x7fd060660450>), (u'TRANSP', vText('TRANSPARENT'))]), Event([(u'CREATED', <icalendar.prop.vDDDTypes at 0x7fd0606608d0>), (u'LAST-MODIFIED', <icalendar.prop.vDDDTypes at 0x7fd060660910>), (u'DTSTAMP', <icalendar.prop.vDDDTypes at 0x7fd060660ad0>), (u'UID', vText('00ccc052-ac26-4aa3-9105-f02b2e12e497')), (u'SUMMARY', vText('Some other birhtday related thing')), (u'RRULE', vRecur([(u'FREQ', [u'YEARLY'])])), (u'DTSTART', <icalendar.prop.vDDDTypes at 0x7fd060660990>), (u'DTEND', <icalendar.prop.vDDDTypes at 0x7fd060660a10>), (u'TRANSP', vText('TRANSPARENT'))])] ## This prints all items nested under ## events. Every stage in the icalendar.Calendar ## class nests this way, you cannot simply ## access subkeys as if it were an actual dict. cal['VEVENTS']['VALARMs'] ## This is very unfortunate. event.subcomponents [Alarm([(u'ACTION', vText('DISPLAY')), (u'TRIGGER', <icalendar.prop.vDDDTypes at 0x7fd060660a90>), (u'DESCRIPTION', vText('Default Mozilla Description'))]), Alarm([(u'ACTION', vText('DISPLAY')), (u'TRIGGER', <icalendar.prop.vDDDTypes at 0x7fd0606609d0>), (u'DESCRIPTION', vText('Default Mozilla Description'))])]Dates
Dates are pretty straightforward, but you cannot afford to forget about timezones. Fortunately for your ease of calculations, the recommended practice is to always work in the UTC timezone, then convert to your current timezone whenever you are generating user output.
pip install tzlocal pip install pytzimport pytz from tzlocal import get_localzone ## Get your current timezone cur_zone = get_localzone() ## datetime.now from the UTC timezone ## (for any date calculations) pytz.utc.localize( datetime.now() ) ## print the time in your local timezone ## (for logs, user display, etc) print( cur_zone.localize( datetime.now() )
Recurring Events
Recurring events are defined in the key. This can get fairly complex, but thankfully the python dateutils module provides a way to parse rrules.
from dateutil import rrule from datetime import datetime import icalendar events = cal.walk('events') for event in events: rrule_str = event['RRULE'].to_ical() ## Prints rrule as it was read in ical string format dt_rrule = rrule.rrulestr( rrule_str, dtstart=event['DTSTART'].dt, ) dt_rrule.before( datetime.now() ) ## Prints closest recurrence before # now dt_ruule.after( datetime( 2020, 01, 01 ) ) ## Prints next recurrence after 2020/01/01