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