Below is the file 'icalinate.py' from this revision. You can also download the file.

#!/usr/bin/env python

#
# iCalinate : Expose simple information from an iCal
# file so other applications don't have to care.
#
# Copyright (C) 2007 Grahame Bowland
#

import datetime
import vobject
import heapq

class EventWrapper(object):
	def __init__ (self, vevent, offset):
		self.vevent = vevent
		self.offset = offset
		self.rruleset_iter = iter (vevent.getrruleset (addRDate=True))
		self.recur ()

	def recur (self):
		self.next_occurrence = self.rruleset_iter.next()
		if self.next_occurrence and self.offset:
			self.next_occurrence -= self.offset
		return self.next_occurrence != None

	def __cmp__ (self, other_event):
		return cmp (self.next_occurrence, other_event.next_occurrence)

class Upcoming(object):
	def __init__ (self, ical_file, offset=None):
		self.ical = vobject.readOne (open (ical_file))
		self.offset = offset
		self.heap = []
		for vevent in self.ical.vevent_list:
			heapq.heappush (self.heap, EventWrapper (vevent, offset))

	def __iter__ (self):
		while len (self.heap) > 0:
			wrapped = heapq.heappop (self.heap)
			yield wrapped.next_occurrence, wrapped.vevent
			if wrapped.recur ():
				heapq.heappush (self.heap, wrapped)

class iCalinate(object):
	def __init__ (self, *args, **kwargs):
		self.upcoming = Upcoming (*args, **kwargs)
		self.__iter_cache = []

	def summary (self, nevents=10):
		rv = []
		for idx, (occurrence, event) in enumerate (self):
			if idx >= nevents:
				break
			entry = {}
			dtstart = event.getChildValue ('dtstart')
			dtend = event.getChildValue ('dtend')
			duration = event.getChildValue ('duration')
			if not duration and dtstart and dtend:
				duration = dtend - dtstart
			entry['occurrence'] = occurrence
			entry['duration'] = duration
			for attr in ('summary', 'description'):
				entry[attr] = event.getChildValue (attr)
			rv.append (entry)
		return rv

	def __iter__ (self):
		now = datetime.datetime.now ()
		old_count = 0
		for occurrence, event in self.__iter_cache:
			if occurrence >= now:
				break
			else:
				old_count += 1
		self.__iter_cache = self.__iter_cache[old_count:]
		for occurrence, event in self.__iter_cache:
			yield occurrence, event
		for occurrence, event in self.upcoming:
			if occurrence < now:
				continue
			self.__iter_cache.append ((occurrence, event))
			yield occurrence, event

if __name__ == '__main__':
	nated = iCalinate ('ical', offset=datetime.timedelta(hours=-8))
	import pprint
	pprint.pprint (nated.summary())