Python icalendar

From wikinotes

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 pytz
import 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