Search code examples
pythondatetimeweek-number

Get week number with week start day different than monday - Python


I have a dataset with a date column. I want to get the week number associated with each date. I know I can use:

x['date'].isocalendar()[1]

But it gives me the week num with start day = monday. While I need the week to start on a friday.

How do you suggest I go about doing that?


Solution

  • tl;dr

    The sections "ISO Standard" and "What you want" is to clarify your need.

    You could just copy paste the code in the section "Solution" and see if the result is what you want.


    ISO Standard

    Definition

    • Weeks start with Monday.
    • Each week's year is the Gregorian year in which the Thursday falls.

    Result of Python Standard Library datetime

    >>> datetime(2020, 1, 1).isocalendar()
    (2020, 1, 3)  # The 3rd day of the 1st week in 2020
    >>> datetime(2019, 12, 31).isocalendar()
    (2020, 1, 2)  # The 2nd day of the 1st week in 2020
    >>> datetime(2019, 1, 1).isocalendar()
    (2019, 1, 2)
    >>> datetime(2017, 1, 1).isocalendar()
    (2016, 52, 7)
    >>> datetime(2016, 12, 26).isocalendar()
    (2016, 52, 1)
    >>> datetime(2015, 12, 31).isocalendar()
    (2015, 53, 4)
    >>> datetime(2016, 1, 1).isocalendar()
    (2015, 53, 5)
    

    Calendar Sketch

    #                 Mo Tu Wd Th Fr Sa Sn
    # [2019-52w] DEC/ 23 24 25 26 27 28 29 /DEC
    # [2020-1w]  DEC/ 30 31  1  2  3  4  5 /JAN
    
    # [2019-1w]  DEC/ 31  1  2  3  4  5  6 /JAN
    
    # [2016-52w] DEC/ 26 27 28 29 30 31  1 /JAN
    
    # [2015-53w] DEC/ 28 29 30 31  1  2  3 /JAN
    # [2016-1w]  JAN/  4  5  6  7  8  9 10 /JAN 
    

    What You Want

    Definition

    • Weeks start with Friday.
    • Each week's year is the Gregorian year in which the Monday falls.

    Calendar Sketch

    #                 Fr Sa Sn. Mo Tu Wd Th 
    # [2019-51w] DEC/ 20 21 22. 23 24 25 26  /DEC
    # [2019-52w] DEC/ 27 28 29. 30 31  1  2  /JAN
    # [2020-1w]  JAN/  3  4  5.  6  7  8  9  /JAN
    
    # [2018-53w] DEC/ 28 29 30. 31  1  2  3  /JAN
    # [2019-1w]  JAN/  4  5  6.  7  8  9 10  /JAN
    
    # [2016-52w] DEC/ 23 24 25. 26 27 28 29  /DEC
    # [2017-1w]  DEC/ 30 31  1.  2  3  4  5  /JAN
    
    # [2015-52w] DEC/ 25 26 27. 28 29 30 31  /DEC
    # [2016-1w]  JAN/  1  2  3.  4  5  6  7  /JAN 
    

    Solution

    from datetime import datetime, timedelta
    from enum import IntEnum
    
    WEEKDAY = IntEnum('WEEKDAY', 'MON TUE WED THU FRI SAT SUN', start=1)
    
    class CustomizedCalendar:
    
        def __init__(self, start_weekday, indicator_weekday=None):
            self.start_weekday = start_weekday
            self.indicator_delta = 3 if not (indicator_weekday) else (indicator_weekday - start_weekday) % 7
    
        def get_week_start(self, date):
            delta = date.isoweekday() - self.start_weekday
            return date - timedelta(days=delta % 7)
    
        def get_week_indicator(self, date):
            week_start = self.get_week_start(date)
            return week_start + timedelta(days=self.indicator_delta)
    
        def get_first_week(self, year):
            indicator_date = self.get_week_indicator(datetime(year, 1, 1))
            if indicator_date.year == year:  # The date "year.1.1" is on 1st week.
                return self.get_week_start(datetime(year, 1, 1))
            else:  # The date "year.1.1" is on the last week of "year-1".
                return self.get_week_start(datetime(year, 1, 8))
        
        def calculate(self, date):
            year = self.get_week_indicator(date).year
            first_date_of_first_week = self.get_first_week(year)
            diff_days = (date - first_date_of_first_week).days
            return year, (diff_days // 7 + 1), (diff_days % 7 + 1)
    
    if __name__ == '__main__':
        # Use like this:
        my_calendar = CustomizedCalendar(start_weekday=WEEKDAY.FRI, indicator_weekday=WEEKDAY.MON)
        print(my_calendar.calculate(datetime(2020, 1, 2)))
    

    To Test

    We could simply initialize CustomizedCalendar with original ISO settings, and verify if the outcome is the same with original isocalendar()'s result.

    my_calendar = CustomizedCalendar(start_weekday=WEEKDAY.MON)
    s = datetime(2019, 12, 19)
    for delta in range(20):
        print my_calendar.calculate(s) == s.isocalendar()
        s += timedelta(days=1)