nth weekday calculation in Python - whats wrong with this code?

one-liner

You can find the nth weekday with a one liner that uses calendar from the standard library.

import calendar
calendar.Calendar(x).monthdatescalendar(year, month)[n][0]

where:

  • x : the integer representing your weekday (0 is Monday)

  • n : the 'nth' part of your question

  • year, month : the integers year and month

This will return a datetime.date object.

broken down

It can be broken down this way:

calendar.Calendar(x)

creates a calendar object with weekdays starting on your required weekday.

.monthdatescalendar(year, month)

returns all the calendar days of that month.

[n][0]

returns the 0 indexed value of the nth week (the first day of that week, which starts on the xth day).

why it works

The reason for starting the week on your required weekday is that by default 0 (Monday) will be used as the first day of the week and if the month starts on a Wednesday, calendar will consider the first week to start on the first occurrence of Monday (ie. week 2) and you'll be a week behind.

example

If you were to need the third Saturday of September 2013 (that month's US stock option expiry day), you would use the following:

calendar.Calendar(5).monthdatescalendar(2013,9)[3][0]

Your problem is here:

adj = temp.weekday()-week_day

First of all, you are subtracting things the wrong way: you need to subtract the actual day from the desired one, not the other way around.

Second, you need to ensure that the result of the subtraction is not negative - it should be put in the range 0-6 using % 7.

The result:

adj = (week_day - temp.weekday()) % 7

In addition, in your second version, you need to add nth_week-1 weeks like you do in your first version.

Complete example:

def nth_weekday(the_date, nth_week, week_day):
    temp = the_date.replace(day=1)
    adj = (week_day - temp.weekday()) % 7
    temp += timedelta(days=adj)
    temp += timedelta(weeks=nth_week-1)
    return temp

>>> nth_weekday(datetime(2011,8,9), 3, 4)
datetime.datetime(2011, 8, 19, 0, 0)