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

寵の児 提交于 2021-02-18 07:29:01

问题


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?


回答1:


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)



回答2:


Here's the minimal logic:

You just need to add 3 days to a Monday to get to a Thursday. Just add the days to Monday and call the ISO Weeknumber. You'll get the shifted weeknumber.

from datetime import datetime, timedelta

x = datetime(2020, 1, 2) # this is Thursday and week 1 in ISO calendar; should be 1 in custom calendar w/ week starting Thu
y = datetime(2020, 1, 3) # this is Friday and week 1 in ISO calendar; should be 2 in custom calendar
print(x)
print(y)

def weeknum(dt):
    return dt.isocalendar()[1]

def myweeknum(dt):
    offsetdt = dt + timedelta(days=3);  # you add 3 days to Mon to get to Thu 
    return weeknum(offsetdt);

print(weeknum(x));
print(myweeknum(x));

print(weeknum(y));
print(myweeknum(y));

Output:

2020-01-02 00:00:00
2020-01-03 00:00:00
1
1
1
2



回答3:


Copy the functions from below, then weeknumber(2020, 8, 21, 'Tuesday') will give you the number of the week August 21, 2020 falls into, week count starting on Tuesday, days in 2020 before the first Tuesday will have week number 0.

# necessary imports
from datetime import date, timedelta
import time

You can use this answer (which relies on this answer) to the question How can I select all of the Sundays for a year using Python? to get all Mondays, Tuesdays, Wednesdays, ... Sundays in a given year.

A helper function:

def weeknum(dayname):
    if dayname == 'Monday':   return 0
    if dayname == 'Tuesday':  return 1
    if dayname == 'Wednesday':return 2
    if dayname == 'Thursday': return 3
    if dayname == 'Friday':   return 4
    if dayname == 'Saturday': return 5
    if dayname == 'Sunday':   return 6

alternatively, (using this):

def weeknum(dayname):
    return time.strptime('Sunday', "%A").tm_wday

The main function we are going to use:

def alldays(year, whichDayYouWant):
    d = date(year, 1, 1)
    d += timedelta(days = (weeknum(whichDayYouWant) - d.weekday()) % 7)
    while d.year == year:
        yield d
        d += timedelta(days = 7)

Now to get the number of week of a given date, do:

def weeknumber(year, month, day, weekstartsonthisday):
    specificdays = [d for d in alldays(year, weekstartsonthisday)]
    return len([specificday for specificday in specificdays if specificday <= datetime.date(year,month,day)])

specificdays is a list of datetime.date objects in the year being the same weekday as weekstartsonthisday. For example, [d for d in alldays(2020,'Tuesday')] starts like this:

[datetime.date(2020, 1, 7),
 datetime.date(2020, 1, 14),
 datetime.date(2020, 1, 21),
 datetime.date(2020, 1, 28),
 datetime.date(2020, 2, 4),
...

As a reminder, 2020 started like this:

The list [specificday for specificday in specificdays if specificday <= datetime.date(year,month,day)] will contain the list of specificdays (ie Mondays, Tuesdays, ..., whichever you specify) which happened in a given year before your date. The len() of this will give us the number of the week. Days in the year before the first specificday will be in the 0 week.

Few examples:

  • weeknumber(2020,1,1,'Tuesday') returns: 0

  • weeknumber(2020,1,6,'Tuesday') returns: 0

  • weeknumber(2020,1,7,'Tuesday') returns: 1

  • weeknumber(2020,12,31,'Tuesday') returns: 52

  • weeknumber(2020,1,1,'Wednesday') returns: 1

Seems good.




回答4:


If you want every date's year is exactly the date itself's year, there's another form of week definition as follows.

If a week starts from Monday

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

# [2018-53w] DEC/ 31  
# [2019-1w]  JAN/     1  2  3  4  5  6

If a week starts from Friday

#                 Fr Sa Sn. Mo Tu Wd Th 
# [2019-53w] DEC/ 27 28 29. 30 31
# [2020-1w]  JAN/                  1  2
# [2020-2w]  JAN/  3  4  5.  6  7  8  9

# [2018-53w] DEC/ 28 29 30. 31  
# [2019-1w]  JAN/               1  2  3
# [2019-2w]  JAN/  4  5  6.  7  8  9 10

Solution

from datetime import datetime, timedelta
from enum import IntEnum

WEEKDAY = IntEnum('WEEKDAY', 'MON TUE WED THU FRI SAT SUN', start=1)

def get_week_number(start, date):
    year_start = datetime(date.year, 1, 1) - timedelta(days=(datetime(date.year, 1, 1).isoweekday() - start) % 7)
    return date.year, (date-year_start).days // 7 + 1, (date-year_start).days % 7 + 1

if __name__ == '__main__':
    # usage:
    print(get_week_number(WEEKDAY.FRI, datetime(2018, 12, 19)))


来源:https://stackoverflow.com/questions/60816403/get-week-number-with-week-start-day-different-than-monday-python

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!