Recurring monthly events in Python

Command Line Warriors - Sat, 09/08/2008 - 11:13am

I was working on something in one of my little Django sites and wondered how you make a recurring monthly event in Python? What I mean by recurring event is "every fourth Saturday" or "every first and second Wednesday" and so on.

I did not want to make a dependency on some huge calender server module like Calcore or Twisted's caldav. All I wanted was a function that accepts "every fourth Saturday" and returns me an actual date that I can use for scheduling things.

A quick google didn't come up with anything, so I decided to do it myself. Here are my first and second attempts. The first attempt just works it out mathematically, the second attempt uses a module from the Python standard library.

"""Helper for recurring date."""

DAYS = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday',
]

from datetime import date

def eventdate(year, month, target_day, target_ordinal):
    """Convert a human event date to a real date.
    For example, 'the third Thursday of the month'
    the target_ordinal is 3 and the target_day is 'Thursday'.
    """

    day = DAYS.index(target_day.title())

    match = 0
    for i in range(1, 32):
        try:
            if date(year, month, i).weekday() == day:
                match += 1
                if match == target_ordinal:
                    return date(year, month, i)

        except ValueError:
            return None

def main():
    """Example when called directly."""
    today = date.today()
    if today.month == 12:
        year = today.year + 1
        month = 1
    else:
        year = today.year
        month = today.month + 1
    print "Next Month's Linux group is", eventdate(year, month, 'Thursday', 3)
    print "Next Month's Python group is", eventdate(year, month, 'Saturday', 4)

# start the ball rolling
if __name__ == "__main__":
    main()

That worked quite fine, but like all good Python programmers, I want to be as efficient (/lazy) as possible, surely the standard library can do this for me? Well I found that the calendar module will return a matrix of dates organised by week and day. This works as follows:

"""Event helpers."""

def eventdate(year, month, target_day, target_ordinal):
    """Convert a human event date to a real date.
    For example, 'the third Thursday of the month'
    the target_ordinal is 3 and the target_day is 'Thursday'.
    """

    import calendar
    day = getattr(calendar, target_day.upper())
    cal = calendar.Calendar()
    return cal.monthdatescalendar(year, month)[target_ordinal - 1][day]

def main():
    """Example when called directly."""
    from datetime import date
    today = date.today()
    if today.month == 12:
        year = today.year + 1
        month = 1
    else:
        year = today.year
        month = today.month + 1
    print "Next Month's Linux group is", eventdate(year, month, 'Thursday', 3)
    print "Next Month's Python group is", eventdate(year, month, 'Saturday', 4)

# start the ball rolling

if __name__ == "__main__":
    main()

This seems to work identically as the above but in less lines of code. I still get the feeling I am trying too hard and I am missing something obvious, but maybe I am just being too much of perfectionist (as always).

If anyone knows or can work out a more efficient method, please do let me know.

Discuss this post - Leave a comment