Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 254
Tom Avatar asked Nov 20 '08 03:11

Tom


5 Answers

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)
like image 110
Ben James Avatar answered Oct 31 '22 13:10

Ben James


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()
like image 17
Martijn Pieters Avatar answered Oct 31 '22 14:10

Martijn Pieters


import datetime

def iso_to_gregorian(iso_year, iso_week, iso_day):
    "Gregorian calendar date for the given ISO year, week and day"
    fourth_jan = datetime.date(iso_year, 1, 4)
    _, fourth_jan_week, fourth_jan_day = fourth_jan.isocalendar()
    return fourth_jan + datetime.timedelta(days=iso_day-fourth_jan_day, weeks=iso_week-fourth_jan_week)

This was adapted from @BenJames's very good answer. You don't have to know the first day of the year. You just have to know an example of a date which is certainly in the same ISO year, and the ISO calendar week and day of that date.

The 4th of Jan is simply one example, because, as Ben pointed out, the 4th of Jan always belongs to the same ISO year and Gregorian year, and is the first day of the year to do so.

Since weeks are all the same length, you can simply subtract the days and weeks between the ISO of the date you want, and the ISO of the date which you know in both forms, and add on that number of days and weeks. (It doesn't matter whether these numbers are positive or negative, so you could choose some other 'fixed day' such as Dec 28th.)

Edit

I corrected this because, as was helpfully pointed by @JoSo, the first day of the Gregorian year which also belongs to the ISO year is Jan 4th not Jan 5th. As the explanation says, it doesn't matter which date is chosen as a reference point, but choosing the Jan 4th makes this choice less 'magic'.

like image 10
jwg Avatar answered Oct 31 '22 14:10

jwg


For the next people coming here, a shorter, single-def, version of Ben's good solution:

def iso_to_gregorian(iso_year, iso_week, iso_day):
    jan4 = datetime.date(iso_year, 1, 4)
    start = jan4 - datetime.timedelta(days=jan4.isoweekday()-1)
    return start + datetime.timedelta(weeks=iso_week-1, days=iso_day-1)
like image 4
Jo So Avatar answered Oct 31 '22 14:10

Jo So


Starting in Python 3.6, datetime.strptime() will support the %G, %V and %u directives, so you can simply do datetime.strptime('2015 1 2', '%G %V %u').date(). See: https://hg.python.org/cpython/rev/acdebfbfbdcf

like image 3
Erik Cederstrand Avatar answered Oct 31 '22 13:10

Erik Cederstrand