Programming a calendar sounds deceptively easy. And it is, until you come to realise that there's very little point in displaying a calendar that doesn't show information about events and periods. You have a potentially overlapping set of periods to display, each spanning days or months. It becomes much more complicated.
At the moment I'm programming a calendar for the booking of accommodation, which is particularly complicated because a) you book nights, not days, and month planners have cells for days, not nights, and b) the dates that are available are the dates not booked, not the dates booked.
I'm using a simpler approach, converting all calendar periods into a stream of events in date order. The interface between producers and consumers of calendar events looks like this:
class CalendarListener(object): def start_month(self, month): """Called before the first day of the month, and before any periods in that month."""
def end_month(self, month): """Called after the last day of the month, and after any periods in that month."""
def start_day(self, date): """Called once for each day to display"""
def start_period(self, date, period): """Called before the day in which the period begins"""
def end_period(self, date, period): """Ends the previously started period"""
This interface makes it very easy to produce, filter, and consume calendar data. What was previously a complicated process of intersecting, splitting, joining, structuring and outputting date ranges suddenly becomes very simple. All of the events received via this interface are guaranteed to be in chronological order, so no date comparison is needed. Almost all calendar operations can be performed with a simple state machine.
A consumer that renders to HTML, for example, is as simple as this:
class MonthRenderer(CalendarListener): def init(self): self.buf = StringIO()
def start_month(self, month): print >>self.buf, """
%s""" % month.name()
w = month.first_day().weekday() if w: print >>self.buf, '<div class="padding" style="width: %dpx"></div>' % (w * 21)
def end_month(self, month): print >>self.buf, "
def start_day(self, date): print >>self.buf, '%d' % date.day
(Note: date and datetime are standard Python classes. Month, however, is my own class. Also, some people use a table rather than CSS for this; that's obviously a fairly simple alteration.)
It took me quite a few false starts before I realised the relative simplicity and convenience of this pattern, which is why I wanted to recommend this. It's very easy to fall into a trap of building complexity and tackling problems using ever-more complicated calendar classes and processors and never take the step back to find a better approach.
The naïve approach for programming a calendar is to write a function, say,
print_month() which renders a month of a calendar. Then call this 12 times. Then wrap it up in a class so you can subclass it to retrieve a list of events and modify output. This quickly became excessively complicated, as I wrote methods to chop and join periods together, work out what the formatting of each day should be, and render it.