Python - Creating list of week numbers of months using dates

Viewed 701

I have got a start date ('2019-11-18') and an end date ('2021-02-19'). I am trying to create a list of all the weeks of each month that exist between the start and end date. My expected result should be something like this:

list = ['2019.Nov.3','2019.Nov.4', '2019.Nov.5' .... '2021.Feb.2','2021.Feb.3']

If the first or last date of a month lands on a Wednesday, i will assume that the week belongs to this month (As 3 out of the 5 working days will belong to this month)

I was actually successful in creating a dataframe with all the weeks of the year that exist between the start and end date using the following code:

date_1 = '18-11-19'

first_date = datetime.strptime(date_1, '%d-%m-%y')

date_2 = '19-02-21'

last_date = datetime.strptime(date_2, '%d-%m-%y')

timeline = pd.DataFrame(columns=['Year', 'Week'])


def create_list(df):
    start_year = int(first_date.isocalendar()[0])
    start_week = int(first_date.isocalendar()[1])
    end_year = int(last_date.isocalendar()[0])
    end_week = int(last_date.isocalendar()[1])

    while start_year < (end_year + 1):

        if start_year == end_year:

            while start_week < (end_week + 1):

                if len(str(start_week)) == 1:

                    week = f'{start_year}' + '.0' + f'{start_week}'

                else:

                    week = f'{start_year}' + '.' + f'{start_week}'

                df = df.append(({'Year': start_year, 'Week': week}), ignore_index=True)
                start_week += 1

        else:

            while start_week < 53:

                if len(str(start_week)) == 1:

                    week = f'{start_year}' + '.0' + f'{start_week}'

                else:

                    week = f'{start_year}' + '.' + f'{start_week}'

                df = df.append(({'Year': start_year, 'Week': week}), ignore_index=True)
                start_week += 1

        start_year += 1
        start_week = 1

    return df


timeline = create_list(timeline)

I was successfully able to use this as an x axis for my line graph. However, the axis is a bit hard to read and its very difficult to know which week belongs to which month.

I would really appreciate if someone can give me a hand with this!

Edit:

So here is the solution with the guidance of @Serge Ballesta. I hope it helps anyone who might need something similiar in the future!

import pandas as pd
import dateutil.relativedelta
from datetime import datetime


def year_week(date):
    if len(str(date.isocalendar()[1])) == 1:

        return f'{date.isocalendar()[0]}' + '.0' + f'{date.isocalendar()[1]}'

    else:

        return f'{date.isocalendar()[0]}' + '.' + f'{date.isocalendar()[1]}'


date_1 = '18-11-19'

first_date = datetime.strptime(date_1, '%d-%m-%y')

date_2 = '19-02-21'

last_date = datetime.strptime(date_2, '%d-%m-%y')

set_first_date = str((first_date - dateutil.relativedelta.relativedelta(months=1)).date())

set_last_date = str((last_date + dateutil.relativedelta.relativedelta(months=1)).date())

s = pd.date_range(set_first_date, set_last_date, freq='W-WED'
                  ).to_series(name='wed').reset_index(drop=True)

df = s.to_frame()

df['week'] = df.apply(lambda x: year_week(x['wed']), axis=1)

df = df.assign(week_of_month=s.groupby(s.dt.strftime('%Y%m')
                                       ).cumcount() + 1)

df = df[(s >= pd.Timestamp('2019-11-18'))
        & (s <= pd.Timestamp('2021-02-19'))]

df['month_week'] = (df['wed'].dt.strftime('%Y.%b.') + df['week_of_month'].astype(str)).tolist()

df = df.drop(['wed', 'week_of_month'], axis = 1)

print (df)

Printed df:

       week  month_week
4   2019.47  2019.Nov.3
5   2019.48  2019.Nov.4
6   2019.49  2019.Dec.1
7   2019.50  2019.Dec.2
8   2019.51  2019.Dec.3
..      ...         ...
65  2021.03  2021.Jan.3
66  2021.04  2021.Jan.4
67  2021.05  2021.Feb.1
68  2021.06  2021.Feb.2
69  2021.07  2021.Feb.3
3 Answers

This may not give you exactly what you need (because of 3 out of 5 days in the last week condition), but may be you can get an idea on how to tweak it to get your desired result.

You can export column res to list with df['res'].to_list()

df = pd.DataFrame({'date': pd.date_range('2019-11-18','2021-02-19', freq=pd.offsets.Week(weekday=0))})
df['year_wk']= df.date.apply(lambda x: x.strftime("%W")).astype(int)
df['mon_beg_wk']= df.date.dt.to_period('M').dt.to_timestamp().dt.strftime("%W").astype(int)
df['mon_wk']= df['year_wk']-df['mon_beg_wk']
df['res']= df['date'].dt.strftime("%Y.%b")+'.'+df['mon_wk'].astype(str)
df

Output

    date    year_wk mon_beg_wk  mon_wk  res
0   2019-11-18  46  43  3   2019.Nov.3
1   2019-11-25  47  43  4   2019.Nov.4
2   2019-12-02  48  47  1   2019.Dec.1
3   2019-12-09  49  47  2   2019.Dec.2
4   2019-12-16  50  47  3   2019.Dec.3
5   2019-12-23  51  47  4   2019.Dec.4
6   2019-12-30  52  47  5   2019.Dec.5
7   2020-01-06  1   0   1   2020.Jan.1
8   2020-01-13  2   0   2   2020.Jan.2
9   2020-01-20  3   0   3   2020.Jan.3
10  2020-01-27  4   0   4   2020.Jan.4
11  2020-02-03  5   4   1   2020.Feb.1
12  2020-02-10  6   4   2   2020.Feb.2
13  2020-02-17  7   4   3   2020.Feb.3
14  2020-02-24  8   4   4   2020.Feb.4
15  2020-03-02  9   8   1   2020.Mar.1
16  2020-03-09  10  8   2   2020.Mar.2
17  2020-03-16  11  8   3   2020.Mar.3
18  2020-03-23  12  8   4   2020.Mar.4
19  2020-03-30  13  8   5   2020.Mar.5
20  2020-04-06  14  13  1   2020.Apr.1
21  2020-04-13  15  13  2   2020.Apr.2
22  2020-04-20  16  13  3   2020.Apr.3
23  2020-04-27  17  13  4   2020.Apr.4
24  2020-05-04  18  17  1   2020.May.1
25  2020-05-11  19  17  2   2020.May.2
26  2020-05-18  20  17  3   2020.May.3
27  2020-05-25  21  17  4   2020.May.4
28  2020-06-01  22  22  0   2020.Jun.0
29  2020-06-08  23  22  1   2020.Jun.1
...     ...     ...     ...     ...     ...
36  2020-07-27  30  26  4   2020.Jul.4
37  2020-08-03  31  30  1   2020.Aug.1
38  2020-08-10  32  30  2   2020.Aug.2
39  2020-08-17  33  30  3   2020.Aug.3
40  2020-08-24  34  30  4   2020.Aug.4
41  2020-08-31  35  30  5   2020.Aug.5
42  2020-09-07  36  35  1   2020.Sep.1
43  2020-09-14  37  35  2   2020.Sep.2
44  2020-09-21  38  35  3   2020.Sep.3
45  2020-09-28  39  35  4   2020.Sep.4
46  2020-10-05  40  39  1   2020.Oct.1
47  2020-10-12  41  39  2   2020.Oct.2
48  2020-10-19  42  39  3   2020.Oct.3
49  2020-10-26  43  39  4   2020.Oct.4
50  2020-11-02  44  43  1   2020.Nov.1
51  2020-11-09  45  43  2   2020.Nov.2
52  2020-11-16  46  43  3   2020.Nov.3
53  2020-11-23  47  43  4   2020.Nov.4
54  2020-11-30  48  43  5   2020.Nov.5
55  2020-12-07  49  48  1   2020.Dec.1
56  2020-12-14  50  48  2   2020.Dec.2
57  2020-12-21  51  48  3   2020.Dec.3
58  2020-12-28  52  48  4   2020.Dec.4
59  2021-01-04  1   0   1   2021.Jan.1
60  2021-01-11  2   0   2   2021.Jan.2
61  2021-01-18  3   0   3   2021.Jan.3
62  2021-01-25  4   0   4   2021.Jan.4
63  2021-02-01  5   5   0   2021.Feb.0
64  2021-02-08  6   5   1   2021.Feb.1
65  2021-02-15  7   5   2   2021.Feb.2

I would build a Series of timestamps with a frequency of W-WED to have consistently Wednesday as day of week. That way, we immediately get the correct month for the week.

To have the number of the week in the month, I would start one month before the required start, and use a cumcount on year-month + 1. Then it would be enough to filter only the expected range and properly format the values:

# produce a series of wednesdays starting in 2019-10-01
s = pd.date_range('2019-10-01', '2021-03-31', freq='W-WED'
                  ).to_series(name='wed').reset_index(drop=True)

# compute the week number in the month
df = s.to_frame().assign(week_of_month=s.groupby(s.dt.strftime('%Y%m')
                                                 ).cumcount() + 1)

# filter the required range
df = df[(s >= pd.Timestamp('2019-11-18'))
      & (s <= pd.Timestamp('2021-02-19'))]

# here is the expected list
lst = (df['wed'].dt.strftime('%Y.%b.')+df['week_of_month'].astype(str)).tolist()

lst is as expected:

['2019.Nov.3', '2019.Nov.4', '2019.Dec.1', '2019.Dec.2', '2019.Dec.3', '2019.Dec.4', 
'2020.Jan.1', '2020.Jan.2', '2020.Jan.3', '2020.Jan.4', '2020.Jan.5', '2020.Feb.1',
'2020.Feb.2', '2020.Feb.3', '2020.Feb.4', '2020.Mar.1', '2020.Mar.2', '2020.Mar.3',
'2020.Mar.4', '2020.Apr.1', '2020.Apr.2', '2020.Apr.3', '2020.Apr.4', '2020.Apr.5',
'2020.May.1', '2020.May.2', '2020.May.3', '2020.May.4', '2020.Jun.1', '2020.Jun.2',
'2020.Jun.3', '2020.Jun.4', '2020.Jul.1', '2020.Jul.2', '2020.Jul.3', '2020.Jul.4',
'2020.Jul.5', '2020.Aug.1', '2020.Aug.2', '2020.Aug.3', '2020.Aug.4', '2020.Sep.1',
'2020.Sep.2', '2020.Sep.3', '2020.Sep.4', '2020.Sep.5', '2020.Oct.1', '2020.Oct.2',
'2020.Oct.3', '2020.Oct.4', '2020.Nov.1', '2020.Nov.2', '2020.Nov.3', '2020.Nov.4',
'2020.Dec.1', '2020.Dec.2', '2020.Dec.3', '2020.Dec.4', '2020.Dec.5', '2021.Jan.1',
'2021.Jan.2', '2021.Jan.3', '2021.Jan.4', '2021.Feb.1', '2021.Feb.2', '2021.Feb.3']

I used datetime.timedelta to do this. It is supposed to work for all start and end dates.

import datetime
import math
date_1 = '18-11-19'

first_date = datetime.datetime.strptime(date_1, '%d-%m-%y')

date_2 = '19-02-21'


last_date = datetime.datetime.strptime(date_2, '%d-%m-%y')

start_week_m=math.ceil(int(first_date.strftime("%d"))/7)#Week number of first month
daysTill_nextWeek=7-int(first_date.strftime("%w"))#Number of days to next sunday.

date_template='%Y.%b.'
tempdate=first_date
weeks=['%s%d' % (tempdate.strftime(date_template),start_week_m)]
tempdate=tempdate+datetime.timedelta(days=daysTill_nextWeek)#tempdate becomes the next sunday
while tempdate < last_date:
    temp_year,temp_month=int(tempdate.strftime("%Y")),int(tempdate.strftime("%m"))
    print(start_week_m)
    weeks.append('%s%d' % (tempdate.strftime(date_template),start_week_m+1))
    start_week_m+=1
    tempdate=tempdate+datetime.timedelta(days=7)
    if temp_month != int(tempdate.strftime("%m")):
        print(temp_year,int(tempdate.strftime("%Y")))
        start_week_m=0
print(weeks)

prints

['2019.Nov.3', '2019.Nov.4', '2019.Dec.1', '2019.Dec.2', '2019.Dec.3', '2019.Dec.4', '2019.Dec.5', '2020.Jan.1', '2020.Jan.2', '2020.Jan.3', '2020.Jan.4', '2020.Feb.1', '2020.Feb.2', '2020.Feb.3', '2020.Feb.4', '2020.Mar.1', '2020.Mar.2', '2020.Mar.3', '2020.Mar.4', '2020.Mar.5', '2020.Apr.1', '2020.Apr.2', '2020.Apr.3', '2020.Apr.4', '2020.May.1', '2020.May.2', '2020.May.3', '2020.May.4', '2020.May.5', '2020.Jun.1', '2020.Jun.2', '2020.Jun.3', '2020.Jun.4', '2020.Jul.1', '2020.Jul.2', '2020.Jul.3', '2020.Jul.4', '2020.Aug.1', '2020.Aug.2', '2020.Aug.3', '2020.Aug.4', '2020.Aug.5', '2020.Sep.1', '2020.Sep.2', '2020.Sep.3', '2020.Sep.4', '2020.Oct.1', '2020.Oct.2', '2020.Oct.3', '2020.Oct.4', '2020.Nov.1', '2020.Nov.2', '2020.Nov.3', '2020.Nov.4', '2020.Nov.5', '2020.Dec.1', '2020.Dec.2', '2020.Dec.3', '2020.Dec.4', '2021.Jan.1', '2021.Jan.2', '2021.Jan.3', '2021.Jan.4', '2021.Jan.5', '2021.Feb.1', '2021.Feb.2']
Related