What's the best way to find the inverse of datetime.isocalendar()?

The Python datetime.isocalendar() method returns a tuple (ISO_year, ISO_week_number, ISO_weekday) for the given datetime object. Is there a corresponding inverse function? If not, is there an easy way to compute a date given a year, week number and day of the week?


Python 3.8 added the fromisocalendar() method:

>>> datetime.fromisocalendar(2011, 22, 1)
datetime.datetime(2011, 5, 30, 0, 0)

Python 3.6 added the %G, %V and %u directives:

>>> datetime.strptime('2011 22 1', '%G %V %u')
datetime.datetime(2011, 5, 30, 0, 0)

Original answer

I recently had to solve this problem myself, and came up with this solution:

import datetime

def iso_year_start(iso_year):
    "The gregorian calendar date of the first day of the given ISO year"
    fourth_jan = datetime.date(iso_year, 1, 4)
    delta = datetime.timedelta(fourth_jan.isoweekday()-1)
    return fourth_jan - delta 

def iso_to_gregorian(iso_year, iso_week, iso_day):
    "Gregorian calendar date for the given ISO year, week and day"
    year_start = iso_year_start(iso_year)
    return year_start + datetime.timedelta(days=iso_day-1, weeks=iso_week-1)

A few test cases:

>>> iso = datetime.date(2005, 1, 1).isocalendar()
>>> iso
(2004, 53, 6)
>>> iso_to_gregorian(*iso)
datetime.date(2005, 1, 1)

>>> iso = datetime.date(2010, 1, 4).isocalendar()    
>>> iso
(2010, 1, 1)
>>> iso_to_gregorian(*iso)
datetime.date(2010, 1, 4)

>>> iso = datetime.date(2010, 1, 3).isocalendar()
>>> iso
(2009, 53, 7)
>>> iso_to_gregorian(*iso)
datetime.date(2010, 1, 3)

As of Python 3.6, you can use the new %G, %u and %V directives. See issue 12006 and the updated documentation:

%G
ISO 8601 year with century representing the year that contains the greater part of the ISO week (%V).

%u
ISO 8601 weekday as a decimal number where 1 is Monday.

%V
ISO 8601 week as a decimal number with Monday as the first day of the week. Week 01 is the week containing Jan 4.

Given a string with year, weeknumber and weekday number, it is easy to parse those out to a date with:

from datetime import datetime

datetime.strptime('2002 01 1', '%G %V %u').date()

or as a function with integer inputs:

from datetime import datetime

def date_from_isoweek(iso_year, iso_weeknumber, iso_weekday):
    return datetime.strptime(
        '{:04d} {:02d} {:d}'.format(iso_year, iso_weeknumber, iso_weekday),
        '%G %V %u').date()