【问题标题】:How to get the week number of the current quarter in Python?如何在 Python 中获取当前季度的周数?
【发布时间】:2020-10-30 07:30:11
【问题描述】:

我已经解决了每一个问题,每一个第三方图书馆都试图找出一种方法来做到这一点,而我不必手动映射日期。

我正在尝试获取当前财政季度的周数。 每个季度从 1 月、4 月、7 月或 10 月的 1 日开始。

给定一个日期(字符串或对象,没关系),我需要能够计算它所在财政季度的周数。

为了让事情更复杂一点,财政年度从四月开始。

例如,今天,2020 年 7 月 9 日是本财政季度 (Q2) 的第 2 周,因为该季度从 4 月开始。同样,2020 年 6 月 29 日和 30 日是第 1 季度的第 14 周。

大多数时候格式化库甚至标准库都有像 ISO 日期这样的方法,我可以很好地提取周数。但它是从一年中的第一天开始的星期数。

我不能使用算术简单地删除到当前日期的周数,因为每个季度有不同的周数。季度可能有 12、13 或 14 周,具体取决于年份。

我得到的最接近的是使用 FiscalYear 库,它非常棒,因为它有一个 Fiscal Quarter 类。不幸的是,继承的方法 isoformat() 不适用于它。只有 FiscalDate 类,它没有给我我需要的季度。

有人遇到过这种情况吗?有人能指出我正确的方向吗?

我会发布代码 sn-ps 但这只是 Python 中获取当前周数的 100 种方法(截至今天,它是 28)。

我已经尝试在 dateutils 中使用 rrules 和 deltas,但我能得到的最接近的是使用偏移量的第一季度的周数。第二季,分崩离析。

我很乐意使用 pandas 或任何其他 3rd 方库,如果它可以帮助我避免硬编码季度日期,或者,上帝保佑,周数到日期的映射。

非常感谢您在正确方向上的任何帮助。


编辑:以下所有三个答案都以不同的方式为我解决了这个问题。我一直在纠结要给出正确答案的答案,但我把它给了@Paul 的答案,因为这是我作为非高年级学生最能关注的答案。这也是符合我个人用例(我没有提到)的答案,它接收一个日期时间对象并获得结果。所以这给了它优势。向其他提供惊人答案的人表示歉意。我很高兴得到代码,我所希望的只是朝着正确的方向轻推。谢谢大家。

【问题讨论】:

  • 对我来说,如何根据季度定义周数并不完全清楚。我的理解是,不同的组织/标准对周数和季度的定义都不同。例如,2021-01-01 的“当前季度周数”是多少? ISO 说那是 2020 年的第 53 周,而不是 2021 年的第一周,那是 2020-Q4-W1{3,4},还是你想要 2021-Q1-W01?
  • 谢谢@Paul 在这种情况下,周数是从 4 月、7 月、10 月和 1 月的每一天开始的日历周的简单增量。所以每个月的第一天总是第一周。那一周有多少天取决于下周一有多快。然后是第 2 周。另一方面,只要日历月转到下个季度的 1 日,第 14 周就会结束。它与 ISO 周数无关,这就是为什么我在这方面遇到这么多麻烦的原因。所以假设 20 年 9 月 7 日,我需要第 2 季度,第 2 周。
  • 那么您每年将有多达 8 个部分周?如果 6 月 30 日是星期二(今年也是如此),那么 6 月 30 日是 Q2 的第 14 周,而 7 月 1 日星期三是 Q3 的第 1 周?
  • 是的,几乎,。由于 Q1 于 4 月开始,6 月 30 日是 Q1 的第 14 周。 7 月 1 日是第二季度的第 1 周。

标签: python date python-datetime python-dateutil


【解决方案1】:

除非这是一种非常常见的计算周数的方法,否则我不知道您是否会找到一个完全适合您的库,但使用 dateutil 的 @987654324 很容易完成@ 和一点逻辑。这是一个返回元组(quarter, week) 的简单实现。既然你说 Q1 从 4 月 1 日开始,我假设从 1 月 1 日到 4 月 1 日这段时间称为 Q0:

from datetime import date, datetime, timedelta
import typing

from dateutil import relativedelta

NEXT_MONDAY = relativedelta.relativedelta(weekday=relativedelta.MO)
LAST_MONDAY = relativedelta.relativedelta(weekday=relativedelta.MO(-1))
ONE_WEEK = timedelta(weeks=1)


def week_in_quarter(dt: datetime) -> typing.Tuple[int, int]:
    d: date = dt.date()
    year = d.year

    # Q0 = January 1, Q1 = April 1, Q2 = July 1, Q3 = October 1
    quarter = ((d.month - 1) // 3)
    quarter_start = date(year, (quarter * 3) + 1, 1)
    quarter_week_2_monday = quarter_start + NEXT_MONDAY

    if d < quarter_week_2_monday:
        week = 1
    else:
        cur_week_monday = d + LAST_MONDAY
        week = int((cur_week_monday - quarter_week_2_monday) / ONE_WEEK) + 2

    return quarter, week

返回:

$ python week_in_quarter.py 
2020-01-01: Q0-W01
2020-02-01: Q0-W05
2020-02-29: Q0-W09
2020-03-01: Q0-W09
2020-06-30: Q1-W14
2020-07-01: Q2-W01
2020-09-04: Q2-W10
2020-12-31: Q3-W14

如果我误解了日历年的第一季度,实际上 X 年的 1 月 1 日至 4 月 1 日被认为是 X-1 年的第四季度,那么您可以更改 return quarter, week 行到此结束(并更改返回类型注释):

if quarter == 0:
    year -= 1
    quarter = 4

return year, quarter, week

这会将返回值更改为:

$ python week_in_quarter.py 
2020-01-01: FY2019-Q4-W01
2020-02-01: FY2019-Q4-W05
2020-02-29: FY2019-Q4-W09
2020-03-01: FY2019-Q4-W09
2020-06-30: FY2020-Q1-W14
2020-07-01: FY2020-Q2-W01
2020-09-04: FY2020-Q2-W10
2020-12-31: FY2020-Q3-W14

如果这是一个速度瓶颈,那么编写一个不使用dateutil.relativedelta的优化版本应该很容易,而是根据星期几、一年中的哪一天以及是否这是闰年(如果您可以在流程中尽早将其转换为整数运算,Python 中的日历计算通常会更快),但我怀疑在大多数情况下,这个版本应该是最容易阅读和理解的。

如果你想避免对dateutil的依赖,你可以用简单的函数替换NEXT_MONDAYLAST_MONDAY

def next_monday(dt: date) -> date:
    weekday = dt.weekday()
    return dt + timedelta(days=(7 - weekday) % 7)

def last_monday(dt: date) -> date:
    weekday = dt.weekday()
    return dt - timedelta(days=weekday)

在这种情况下,您可以将两个_monday 变量分别分配为quarter_week_2_monday = next_monday(quarter_start)cur_week_monday = last_monday(dt)

注意:如果我正在编写这个函数,我可能不会让它返回一个裸整数元组,而是使用attrsdataclass 来创建一个简单的类,就像这样:

import attr

@attr.s(auto_attribs=True, frozen=True, slots=True)
class QuarterInWeek:
    year: int
    quarter: int
    week: int

    def __str__(self):
        return f"FY{self.year}-Q{self.quarter}-W{self.week:02d}"

(请注意,slots=True 是可选的,如果您使用 dataclasses.dataclass 代替,我认为不可用 - 只是这是一个简单的结构,我倾向于将插槽类用于简单的结构)。

【讨论】:

  • 不,保罗,你没有误会。这是完全正确的。 1 月至 4 月为上一财政年度。你的代码对我有用。这是包含代码的 3 个不同的正确响应。我在我的元素。另外,感谢您提供有关数据类的信息(如您所知,我不是很有经验!)这就是我正在采用的方法,因为我会经常使用它。
  • 我假设您不关心依赖关系,所以我在这里使用了dateutil,但是如果您希望避免任何依赖关系,我已经更新了答案以添加两个简单的函数来替换需要dateutil
  • 对不起,我的意思是更多的依赖关系。不是因为臃肿,而是因为我自己的理解。 dateutil 很好,但我感谢额外的帮助!
  • 干得好!我喜欢现代的 Python
【解决方案2】:

我认为这可以满足您的需要(或者至少是一个非常好的开始):

import datetime as dt

def quarter(date):
    return (date.month-1)//3 + 1 
    
def week_in_q(d):
    year=d.year
    soq={1:dt.date(year,1,1),
         2:dt.date(year,4,1),
         3:dt.date(year,7,1),
         4:dt.date(year,10,1)}
    for i, sow in enumerate(soq[quarter(d)]+dt.timedelta(weeks=x) for x in range(5*3)):
        if sow>=d: 
            return i+1
date=dt.date(2020, 1, 1)    

for d in (date+dt.timedelta(weeks=x) for x in range(53)):
    print(f"date: {d}, quarter: {quarter(d)}, week in that quarter: {week_in_q(d)}")

打印:

date: 2020-01-01, quarter: 1, week in that quarter: 1
date: 2020-01-08, quarter: 1, week in that quarter: 2
date: 2020-01-15, quarter: 1, week in that quarter: 3
date: 2020-01-22, quarter: 1, week in that quarter: 4
date: 2020-01-29, quarter: 1, week in that quarter: 5
date: 2020-02-05, quarter: 1, week in that quarter: 6
date: 2020-02-12, quarter: 1, week in that quarter: 7
date: 2020-02-19, quarter: 1, week in that quarter: 8
date: 2020-02-26, quarter: 1, week in that quarter: 9
date: 2020-03-04, quarter: 1, week in that quarter: 10
date: 2020-03-11, quarter: 1, week in that quarter: 11
date: 2020-03-18, quarter: 1, week in that quarter: 12
date: 2020-03-25, quarter: 1, week in that quarter: 13
date: 2020-04-01, quarter: 2, week in that quarter: 1
date: 2020-04-08, quarter: 2, week in that quarter: 2
date: 2020-04-15, quarter: 2, week in that quarter: 3
date: 2020-04-22, quarter: 2, week in that quarter: 4
date: 2020-04-29, quarter: 2, week in that quarter: 5
date: 2020-05-06, quarter: 2, week in that quarter: 6
date: 2020-05-13, quarter: 2, week in that quarter: 7
date: 2020-05-20, quarter: 2, week in that quarter: 8
date: 2020-05-27, quarter: 2, week in that quarter: 9
date: 2020-06-03, quarter: 2, week in that quarter: 10
date: 2020-06-10, quarter: 2, week in that quarter: 11
date: 2020-06-17, quarter: 2, week in that quarter: 12
date: 2020-06-24, quarter: 2, week in that quarter: 13
date: 2020-07-01, quarter: 3, week in that quarter: 1
date: 2020-07-08, quarter: 3, week in that quarter: 2
date: 2020-07-15, quarter: 3, week in that quarter: 3
date: 2020-07-22, quarter: 3, week in that quarter: 4
date: 2020-07-29, quarter: 3, week in that quarter: 5
date: 2020-08-05, quarter: 3, week in that quarter: 6
date: 2020-08-12, quarter: 3, week in that quarter: 7
date: 2020-08-19, quarter: 3, week in that quarter: 8
date: 2020-08-26, quarter: 3, week in that quarter: 9
date: 2020-09-02, quarter: 3, week in that quarter: 10
date: 2020-09-09, quarter: 3, week in that quarter: 11
date: 2020-09-16, quarter: 3, week in that quarter: 12
date: 2020-09-23, quarter: 3, week in that quarter: 13
date: 2020-09-30, quarter: 3, week in that quarter: 14
date: 2020-10-07, quarter: 4, week in that quarter: 2
date: 2020-10-14, quarter: 4, week in that quarter: 3
date: 2020-10-21, quarter: 4, week in that quarter: 4
date: 2020-10-28, quarter: 4, week in that quarter: 5
date: 2020-11-04, quarter: 4, week in that quarter: 6
date: 2020-11-11, quarter: 4, week in that quarter: 7
date: 2020-11-18, quarter: 4, week in that quarter: 8
date: 2020-11-25, quarter: 4, week in that quarter: 9
date: 2020-12-02, quarter: 4, week in that quarter: 10
date: 2020-12-09, quarter: 4, week in that quarter: 11
date: 2020-12-16, quarter: 4, week in that quarter: 12
date: 2020-12-23, quarter: 4, week in that quarter: 13
date: 2020-12-30, quarter: 4, week in that quarter: 14

【讨论】:

  • 感谢您的帮助。这真的很有帮助。我遇到的问题是第一季度从 4 月 1 日开始。因此,输出中的所有季度都应该加一。因为您使用当前年份的实例来基于季度映射,所以当它移到下一年时,我会失败:(如果我只是修改映射并添加4:dt.date(year + 1,1,1)},这会将所有周值更改为 1。 date: 2020-04-01, quarter: 2, week in that quarter: 1date: 2020-04-08, quarter: 2, week in that quarter: 1
  • 这些都是相对较小的变化。打赌你能弄明白!
  • 谢谢!我有足够的工作,我在正确的轨道上。非常感谢您对此提供的帮助!
  • 终于也搞定了。对于我的用例,我修改了错误的行。我需要做的唯一调整是第一种方法。所以非常感谢你!我无法告诉你我多么感谢你(和其他海报)努力提供代码。我没想到。我也非常感谢没有外部依赖。
【解决方案3】:

这是一个使用python的isocalendar库查找周数的简单解决方案:

注意:每周从星期一开始。

from datetime import datetime

FISCAL_QUARTERS = [4, 7, 10, 1]  # April, July, October, January
FISCAL_PERIOD = 3

def _calc_quarter_week(day, month, year):
    fiscal_quarter = None
    # Find which quarter the given date falls in
    for fiscal_index in range(len(FISCAL_QUARTERS)):
        f_month = FISCAL_QUARTERS[fiscal_index]
        if month >= f_month and month < f_month + FISCAL_PERIOD:
            fiscal_quarter = fiscal_index + 1
            break

    quarter_start_day = datetime(
        year=year, month=FISCAL_QUARTERS[fiscal_quarter-1], day=1)
    # Quarter week number
    _, q_week_no, _ = quarter_start_day.isocalendar()

    given_date = datetime(year=year, month=month, day=day)
    # Given week number
    _, given_week_no, _ = given_date.isocalendar()

    return fiscal_quarter, given_week_no - q_week_no + 1


day, month, year = map(int, input('Day Month Year\n').strip().split())
fiscal_quarter, week_count = _calc_quarter_week(day, month, year)
print('Fiscal quarter: {}, Week: {}'.format(fiscal_quarter, week_count))

输出:

Day Month Year
29 6 2020
Fiscal quarter: 1, Week: 14
Day Month Year
9 7 2020
Fiscal quarter: 2, Week: 2

【讨论】:

  • 这在某些日期对我有用(谢谢!)。 9 7 2020 有效,但 29 6 2020 无效。 Line 17, in _calc_quarter_week quarter_start_day = datetime(year=year, month=FISCAL_QUARTERS[fiscal_quarter - 1], day=1) TypeError: unsupported operand type(s) for -: 'NoneType' and 'int' 我不确定为什么第一个 for 循环在某些情况下没有正确设置财政季度,将其保留为无。至少这是我认为正在发生的事情。有什么想法吗?
  • 我认为您可能放错了日期和月份输入。 29 个月和 6 天?
  • 我怀疑我的问题现在出在其他问题上。这现在对我有用,所以非常感谢!我仍然不明白你如何在嵌套条件中计算它的逻辑,但我现在有一些东西要重写和玩。非常感谢您努力从头开始编写代码。我根本没有寻找或期待它!
  • @DaveDavis 我基本上是将给定月份与财政开始月份进行比较,以找到给定日期所在的财政季度。例如第一个财政季度涵盖第 4、5、6 个月。要确定给定月份是否属于财政季度,只需检查给定月份是否大于财政开始月份且小于下一个财政开始月份,即财政开始月份 + 3(会计期间)。
猜你喜欢
  • 1970-01-01
  • 2012-12-12
  • 1970-01-01
  • 2012-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-02
  • 1970-01-01
相关资源
最近更新 更多