4

I'd like to find the most pythonic way to output a list of the week numbers between two dates.

For example:

input

start = datetime.date(2011, 12, 25) 
end = datetime.date(2012, 1, 21)

output

find_weeks(start, end)
>> [201152, 201201, 201202, 201203]

I've been struggling using the datetime library with little success

Ludo
  • 2,307
  • 2
  • 27
  • 58

5 Answers5

7

Something in the lines of (update: removed less-readable option)

import datetime

def find_weeks(start,end):
    l = []
    for i in range((end-start).days + 1):
        d = (start+datetime.timedelta(days=i)).isocalendar()[:2] # e.g. (2011, 52)
        yearweek = '{}{:02}'.format(*d) # e.g. "201152"
        l.append(yearweek)
    return sorted(set(l))

start = datetime.date(2011, 12, 25) 
end = datetime.date(2012, 1, 21)

print(find_weeks(start,end)[1:]) # [1:] to exclude first week.

Returns

['201152', '201201', '201202', '201203']

To include the first week (201151) simply remove [1:] after function call

Anton vBR
  • 18,287
  • 5
  • 40
  • 46
  • Interesting approach! Seems like the "more Pythonic" version that OP was asking for, +1 – asongtoruin Feb 22 '18 at 12:53
  • @asongtoruin Thanks. You also had something nice going but I think it is more safe to just use all the days and set(). – Anton vBR Feb 22 '18 at 12:55
  • Thanks both. Possibly even more pythonic would be to turn it into a one-liner list comprehension. Harder to understand what's going on though. – Ludo Feb 22 '18 at 13:12
  • @Ludo I had a listcomprehension in the beginning, but when it can't fit one line I don't think it is pythonic anymore (readability first!). In light of that I separated way the format line. – Anton vBR Feb 22 '18 at 13:20
  • @Ludo being "Pythonic" isn't necessarily the be-all and end-all. Readability is important too - I think this answer has a good mix of being concise but very understandable. – asongtoruin Feb 22 '18 at 14:24
3

.isocalendar() is your friend here - it returns a tuple of (year, week of year, day of week). We use that to reset the start date to the start of th eweek, and then add on a week each time until we pass the end date:

import datetime


def find_weeks(start_date, end_date):
    subtract_days = start_date.isocalendar()[2] - 1
    current_date = start_date + datetime.timedelta(days=7-subtract_days)
    weeks_between = []
    while current_date <= end_date:
        weeks_between.append(
            '{}{:02d}'.format(*current_date.isocalendar()[:2])
        )
        current_date += datetime.timedelta(days=7)
    return weeks_between

start = datetime.date(2011, 12, 25)
end = datetime.date(2012, 1, 21)

print(find_weeks(start, end))

This prints

['201152', '201201', '201202', '201203']
asongtoruin
  • 9,794
  • 3
  • 36
  • 47
3

Using Pandas

import pandas as pd

dates=pd.date_range(start=start,end=end,freq='W')
date_index=dates.year.astype(str)+dates.weekofyear.astype(str).str.zfill(2)
date_index.tolist()
Anton vBR
  • 18,287
  • 5
  • 40
  • 46
Rayadurai
  • 66
  • 6
  • 1
    I like the pandas format solution for readability, but runs slower than the datetime solution: datetime: 46.9 µs ± 442 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each) pandas: 883 µs ± 30.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) – Ludo Feb 22 '18 at 13:20
  • I changed the format... but this solution still doesn't include the last week. – Anton vBR Feb 22 '18 at 13:32
0

I suggest you the following easy-to-read solution:

import datetime

start = datetime.date(2011, 12, 25) 
end = datetime.date(2012, 1, 21)

def find_weeks(start, end):
    l = []
    while (start.isocalendar()[1] != end.isocalendar()[1]) or (start.year != end.year):
        l.append(start.isocalendar()[1] + 100*start.year)
        start += datetime.timedelta(7)
    l.append(start.isocalendar()[1] + 100*start.year)
    return (l[1:])


print(find_weeks(start, end))

>> [201252, 201201, 201202, 201203]
Laurent H.
  • 6,316
  • 1
  • 18
  • 40
0

I prefer the arrow style solution here (might need pip install arrow):

import arrow

start = arrow.get('2011-12-25')
end = arrow.get('2012-01-21')
weeks = list(arrow.Arrow.span_range('week', start, end))

result looks like this:

>> from pprint import pprint
>> pprint(weeks[1:])
[(<Arrow [2011-12-19T00:00:00+00:00]>,
  <Arrow [2011-12-25T23:59:59.999999+00:00]>),
 (<Arrow [2011-12-26T00:00:00+00:00]>,
  <Arrow [2012-01-01T23:59:59.999999+00:00]>),
 (<Arrow [2012-01-02T00:00:00+00:00]>,
  <Arrow [2012-01-08T23:59:59.999999+00:00]>),
 (<Arrow [2012-01-09T00:00:00+00:00]>,
  <Arrow [2012-01-15T23:59:59.999999+00:00]>),
 (<Arrow [2012-01-16T00:00:00+00:00]>,
  <Arrow [2012-01-22T23:59:59.999999+00:00]>)]

from there you can change the output to match the year and week number.

TheGary
  • 1
  • 3