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()
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'.
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)
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
Note that %W is the week # (0-53) which is NOT THE SAME as the ISO week (1-53). There will be edge cases where %W will not work.
I came up with a solution similar to the one posted by Ben James, but using a single function:
import datetime
def getDateFromWeek(year,week,day):
"""Method to retrieve the date from the specified week, year and weekday"""
year_start = datetime.date(year,1,1)
ys_weekday = year_start.weekday()
delta = (week*7)+(day-ys_weekday)
if ys_weekday<4:
delta -= 7
return year_start + datetime.timedelta(days=delta)
I tested it out with boundary values such as the last week of 2020 and first week of 2021, and it worked out pretty well.
EDIT: ignore this, the edge cases are a pain. Go with Ben's solution.
Ok, on closer inspection I noticed that strptime has %W and %w parameters, so the following works:
def fromisocalendar(y,w,d):
return datetime.strptime( "%04dW%02d-%d"%(y,w-1,d), "%YW%W-%w")
A couple of gotchas: The ISO week number starts at 1, while %W starts at 0. The ISO week day starts at 1 (Monday), which is the same as %w, so Sunday would probably have to be 0, not 7...
Related
I have three variables:
week_no = 2
day = 'Fri'
year = 2021
I need to locate the date by using these variables and output as a datetime.date object.
Thanks.
Following code:
datetime.datetime.strptime(f'{week_no} {day} {year}', '%U %a %Y')
Return following result:
datetime.datetime(2021, 1, 15, 0, 0)
You probably want to use datetime.date.fromisocalendar, though this is only available in Python 3.8+.
Return a date corresponding to the ISO calendar date specified by year, week and day. This is the inverse of the function date.isocalendar().
date.fromisocalendar(year, week, day)
The day is specified as an integer between 1 and 7.
>>> import datetime
>>> datetime.date.fromisocalendar(2021, 2, 5)
datetime.date(2021, 1, 15)
Recently I was bothered with the function that would return starting day of the week for a given string concatenation of year and week and surprisingly couldn't find it anywhere.
I saw similar ones (ie. question1, question2), but I am only given the year and week, without days.
Let's have an example input and a desired output:
func('202005') -> Monday 27.01.2020
I found a lot of counting, but not anything elegant, a smooth one-liner. Is there any?
During investigation I combined some similar approaches with python docs regading datetime, so leaving my proposal here that I run with for any bypassers:
# python3.7
import datetime
def get_week_start_date(yearweek: str) -> datetime:
"""
Return date of starting the week (Monday)
i.e: 201944 returns datetime.datetime(2019, 11, 4, 0, 0)
%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.
Arguments:
yearweek {str} -- year to be calculated
Returns:
datetime -- Date of starting the week in passed year, week
"""
return datetime.datetime.strptime(f"{yearweek}-1", "%G%V-%u")
time.strptime accepts %U in format string, which mean 2-digit number of week, however it requires day of week to work and starts counting weeks at 0, that is
import time
yearweek = '202005'
yearweekmonday = yearweek+'1'
print(time.strptime(yearweekmonday, '%Y%U%w'))
Output: time.struct_time(tm_year=2020, tm_mon=2, tm_mday=3, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=0, tm_yday=34, tm_isdst=-1)
As weeks numbers starts with 0, 202005 result in 2020-02-03 rather than 2020-01-27, but after you conver time.struct_time to datatime object from that you should be able to easily manipulate using timedelta.
I'm trying to generate week number string using Python time module, considering week starts on Sunday.
If my interpretation of the official documentation is correct then this can be achieved by the following code:
import time
time.strftime("%U", time.localtime())
>> 37
My question is, is the above output correct? Shouldn't the output be 38 instead, considering the below details:
My timezone is IST (GMT+5:30)
import time
#Year
time.localtime()[0]
>> 2019
#Month
time.localtime()[1]
>> 9
#Day
time.localtime()[2]
>> 18
Yes, the output is correct. Week 1 started on January 6th, as that was the first Sunday in 2019. January 1st through 5th were week 0:
>>> time.strftime('%U', time.strptime("2019-1-1", "%Y-%m-%d"))
'00'
>>> time.strftime('%U', time.strptime("2019-1-6", "%Y-%m-%d"))
'01'
This is covered in the documentation:
All days in a new year preceding the first Sunday are considered to be in week 0.
You are perhaps looking for the ISO week date, but note that in this system the first day of the week is a Monday.
You can get the week number using that system with the datetime.date.isocalendar() method, or by formatting with %V:
>>> time.strftime("%V", time.localtime())
'38'
>>> from datetime import date
>>> date.today().isocalendar() # returns ISO year, week, and weekday
(2019, 38, 2)
>>> date.today().strftime("%V")
'38'
It's correct since you start counting from the first Sunday.
%U - week number of the current year, starting with the first Sunday as the first day of the first week
https://www.tutorialspoint.com/python/time_strftime.htm
It's correct. Since all days in a new year preceding the first Sunday are considered to be in week 0 (01/01 to 01/05), this week is the week 37.
Please what's wrong with my code:
import datetime
d = "2013-W26"
r = datetime.datetime.strptime(d, "%Y-W%W")
print(r)
Display "2013-01-01 00:00:00", Thanks.
A week number is not enough to generate a date; you need a day of the week as well. Add a default:
import datetime
d = "2013-W26"
r = datetime.datetime.strptime(d + '-1', "%Y-W%W-%w")
print(r)
The -1 and -%w pattern tells the parser to pick the Monday in that week. This outputs:
2013-07-01 00:00:00
%W uses Monday as the first day of the week. While you can pick your own weekday, you may get unexpected results if you deviate from that.
See the strftime() and strptime() behaviour section in the documentation, footnote 4:
When used with the strptime() method, %U and %W are only used in calculations when the day of the week and the year are specified.
Note, if your week number is a ISO week date, you'll want to use %G-W%V-%u instead! Those directives require Python 3.6 or newer.
In Python 3.8 there is the handy datetime.date.fromisocalendar:
>>> from datetime import date
>>> date.fromisocalendar(2020, 1, 1) # (year, week, day of week)
datetime.date(2019, 12, 30, 0, 0)
In older Python versions (3.7-) the calculation can use the information from datetime.date.isocalendar to figure out the week ISO8601 compliant weeks:
from datetime import date, timedelta
def monday_of_calenderweek(year, week):
first = date(year, 1, 1)
base = 1 if first.isocalendar()[1] == 1 else 8
return first + timedelta(days=base - first.isocalendar()[2] + 7 * (week - 1))
Both works also with datetime.datetime.
To complete the other answers - if you are using ISO week numbers, this string is appropriate (to get the Monday of a given ISO week number):
import datetime
d = '2013-W26'
r = datetime.datetime.strptime(d + '-1', '%G-W%V-%u')
print(r)
%G, %V, %u are ISO equivalents of %Y, %W, %w, so this outputs:
2013-06-24 00:00:00
Availabe in Python 3.6+; from docs.
import datetime
res = datetime.datetime.strptime("2018 W30 w1", "%Y %W w%w")
print res
Adding of 1 as week day will yield exact current week start. Adding of timedelta(days=6) will gives you the week end.
datetime.datetime(2018, 7, 23)
If anyone is looking for a simple function that returns all working days (Mo-Fr) dates from a week number consider this (based on accepted answer)
import datetime
def weeknum_to_dates(weeknum):
return [datetime.datetime.strptime("2021-W"+ str(weeknum) + str(x), "%Y-W%W-%w").strftime('%d.%m.%Y') for x in range(-5,0)]
weeknum_to_dates(37)
Output:
['17.09.2021', '16.09.2021', '15.09.2021', '14.09.2021', '13.09.2021']
In case you have the yearly number of week, just add the number of weeks to the first day of the year.
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> week = 40
>>> year = 2019
>>> date = datetime.date(year,1,1)+relativedelta(weeks=+week)
>>> date
datetime.date(2019, 10, 8)
Another solution which worked for me that accepts series data as opposed to strptime only accepting single string values:
#fw_to_date
import datetime
import pandas as pd
# fw is input in format 'YYYY-WW'
# Add weekday number to string 1 = Monday
fw = fw + '-1'
# dt is output column
# Use %G-%V-%w if input is in ISO format
dt = pd.to_datetime(fw, format='%Y-%W-%w', errors='coerce')
Here's a handy function including the issue with zero-week.
I am trying to get a date based on a number of the week, but there are some annoyances.
The date.weekday() returns the day of the week where 0 in Monday and 6 is Sunday.
The %w directive of date.strftime() and date.strptime() uses the 0 for Sunday and 6 for Saturday.
This causes some really annoying issues when trying to figure out a date given a week number from date.weekday().
Is there a better way of getting a date from a week number?
EDIT:
Added the example.
import datetime
original_date = datetime.date(2014, 8, 24)
week_of_the_date = original_date.isocalendar()[1] # 34
day_of_the_date = original_date.isocalendar()[2] # 7
temp = '{0} {1} {2}'.format(*(2014, week_of_the_date, day_of_the_date-1))
date_from_week = datetime.datetime.strptime(temp, '%Y %W %w')
week_from_new_date = date_from_week.isocalendar()[1] # 35!!
EDIT 2:
I ultimately put the date stuff in the view (using jQuery UI), it has more consistent notions of weeks.
I think the Sunday vs. Monday distinction between weekday and strftime using %W is moot - you could use isoweekday to get those to line up, or %U in strftime if you wanted Sunday as the first day of the week. The real problem is that strftime, based on the underlying C function, determines the first week of the year differently than the ISO definition. With %W the docs say: " All days in a new year preceding the first Monday are considered to be in week 0". ISO calendars count the week containing the first Thursday as the first week, for reasons I do not understand.
Two ways I found to work with ISO weeks, either just getting datetime.date instances back or supporting a variety of operations, are:
this answer with a simple timedelta approach:
What's the best way to find the inverse of datetime.isocalendar()?
this third-party library: https://pypi.python.org/pypi/isoweek/