【问题标题】:Get week number with week start day different than monday - Python获取与星期一不同的星期开始日的星期数 - Python
【发布时间】:2020-03-23 15:19:52
【问题描述】:

我有一个包含日期列的数据集。我想获取与每个日期关联的周数。 我知道我可以使用:

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

但它给了我开始日 = 星期一的星期数。虽然我需要从星期五开始一周。

你建议我怎么做?

【问题讨论】:

  • 日期加上三天,然后得到周数? (嗯,这样一来,如果一年从星期六或类似的时间开始,那么周数可能会偏移一个。)
  • 根据 ISO 标准 8601 和 ISO 标准 2015,ISO 周将星期四作为一周的中间。因此,您无法真正改变这一天。像@tobias_k 这样的方法表明它可能会奏效,但它不再是真正的 ISO 周了。
  • calendar 模块允许设置firstweekday 但TBH 我不知道如何从datetime 日期转到这种日历...
  • 是的,我尝试使用日历设置第一周,但它不会改变我的代码结果
  • 或者添加天数直到星期五,然后得到一年中的哪一天(如果没有直接的方法,使用strptime%j)并除以7?

标签: python datetime week-number


【解决方案1】:

tl;博士

ISO 标准”和“您想要什么”部分是为了阐明您的需求。

您可以将代码复制粘贴到“解决方案”部分,看看结果是否符合您的要求。


ISO 标准

定义

  • 每周从星期一开始。
  • 每周的年份都是公历,星期四所在的年份。

Python 标准库的结果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)

日历草图

#                 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 

你想要什么

定义

  • 每周从星期五开始。
  • 每周的年份都是公历年,星期一所在的年份。

日历草图

#                 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 

解决方案

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)))

测试

我们可以简单地用原始 ISO 设置初始化CustomizedCalendar,并验证结果是否与原始isocalendar() 的结果相同。

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)

【讨论】:

  • 非常感谢!如果我执行你的代码,我会得到:my_calendar.calculate(datetime(2019, 1, 1)) 给我(2018, 53.57142857142857, 5) - 这是预期的吗?
  • 好的,我明白了:np.floor(my_calendar.calculate(datetime(2019, 12, 28))[1]) 给了我周数作为整数。不是星期五的每一天都是属于上一个星期五所属年份的一周的一部分。每个星期五都属于属于该星期五所在年份的一周 - 如果此评估有误,请告诉我
  • 嗨@zabop,代码最初是用Py2编写的,所以如果你使用Py3执行它,结果将是一个浮点数。我已将代码修改为与 Py3 兼容,现在它应该是一个整数。请再试一次。
  • 我不确定我是否完全理解您的评估。我们可以使用案例2017/1/1 来验证。如果周从Friday 开始,则2017/1/1 的周为2016/12/30(Fri) ~ 2017/1/5(Thu)。如果indicator_weekday 设置为Monday,那么因为那一周的Monday*2017*/1/2,所以2017/1/1 将在2017 的第一周;如果indicator_weekday 设置为Friday,那么因为那一周的Friday*2016*/12/30,所以2017/1/1 将在2016 的最后一周。
【解决方案2】:

这是最基本的逻辑:

您只需将星期一添加 3 天即可到达星期四。只需将天数添加到星期一并调用 ISO 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));

输出:

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

【讨论】:

  • 需要一个微妙的解释。你需要从周一到周五,这是 4 天。您实际上需要减去 4 天。但是 -4 mod 7 是 3,因此您添加。 3 在 timedelta() 上面。
  • 谢谢! myweeknum(datetime(2019,1,1)) 给我 1,如果一周从星期四开始,这不是正确的结果,还是我错过了什么?
  • 2019 1 1 是星期二。无论是哪一天,任何一年的 1/1/yyyy 都应该是第 1 周。
  • datetime.date(2010, 1, 1).isocalendar()[1], from here 给了我 53 - 不过,我喜欢你的代码的简单性!
  • 或者你的定义是从第一个星期五开始,按照你对从周五到周四一周的定义开始一个完整的“周”?
【解决方案3】:

如果您希望每个日期的年份恰好是日期本身的年份,则还有另一种形式的周定义,如下所示。

如果一周从星期一开始

#                 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

如果一周从星期五开始

#                 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

解决方案

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)))

【讨论】:

  • 这对我有用
【解决方案4】:

从下面复制函数,然后weeknumber(2020, 8, 21, 'Tuesday') 将为您提供 2020 年 8 月 21 日所在的周数,周数从 Tuesday 开始,2020 年第一个 Tuesday 之前的天数将具有周数 @ 987654331@.

# necessary imports
from datetime import date, timedelta
import time

您可以对问题How can I select all of the Sundays for a year using Python? 使用this answer(依赖于this answer)来获取给定年份的所有星期一、星期二、星期三、...星期日。

辅助函数:

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

或者,(使用this):

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

我们要使用的主要函数:

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)

现在要获取给定日期的周数,请执行以下操作:

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 是与weekstartsonthisday 在同一工作日的年份中的datetime.date 对象列表。例如,[d for d in alldays(2020,'Tuesday')] 开头是这样的:

[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),
...

提醒一下,2020 年是这样开始的:

列表[specificday for specificday in specificdays if specificday &lt;= datetime.date(year,month,day)] 将包含specificdays 的列表(即星期一、星期二、...,无论您指定什么),这些列表在您的日期之前的给定年份中发生。其中的len() 将为我们提供周数。第一个specificday 前一年的天数将在0 周内。

几个例子:

  • weeknumber(2020,1,1,'Tuesday') 返回:0

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

  • weeknumber(2020,1,7,'Tuesday') 返回:1

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

  • weeknumber(2020,1,1,'Wednesday') 返回:1

看起来不错。

【讨论】:

  • 您的程序将有(2015,12,23,'Friday')(2015,12,29,'Friday') 返回52(2015,12,30,'Friday')(2015,12,31,'Friday') 返回53,和(2016,1,1,'Friday')(2016,1,7,'Friday'),返回1不正确。
  • 嗯,确实看起来很糟糕。有更好的主意吗?
【解决方案5】:

聚会有点晚了,我们是这样解决的

def get_week(date, weekday_start):
    # Number of days since day 1 of the year. First day is day 0.
    number_of_days = date.timetuple().tm_yday - 1
    # Get the first day of the week in int. Monday is 0, Tuesday is 1, etc.
    first_day_of_week = time.strptime(weekday_start if weekday_start else 'MONDAY', "%A").tm_wday
    # Get the first day of the year. isoweekday considers Monday as 1 and Sunday as 2, thus why -1.
    first_day_of_year = (datetime(date.year, 1, 1).isoweekday()) - 1
    # Get the week number
    return (number_of_days + abs(first_day_of_week - first_day_of_year)) // 7 + 1

我们将一周的第一部分视为第 1 周。这意味着如果一年从星期二开始,星期一设置为 weekday_start,则第一周将只有 6 天。

weekday_start 是一个将 MONDAY、TUESDAY、WEDNESDAY 等作为输入的字符串。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-05
    • 1970-01-01
    • 2020-01-09
    • 1970-01-01
    • 2017-07-19
    • 2020-07-02
    • 2020-03-21
    • 2021-12-01
    相关资源
    最近更新 更多