【问题标题】:Python get first and last day of current calendar quarterPython获取当前日历季度的第一天和最后一天
【发布时间】:2016-04-22 12:04:09
【问题描述】:

我已经构建了一个函数来获取当前季度的第一天和最后一天,但它有点啰嗦。我想知道,有没有更简洁的方法来完成这个?

我知道pandas 有一个QuarterBegin() 函数,但我无法以更简洁的方式实现它。

import datetime as dt
from dateutil.relativedelta import relativedelta     

def get_q(first=None,last=None):

    today = dt.date.today()

    qmonth = [1, 4, 7, 10]

    if first:
        for i,v in enumerate(qmonth):
            if (today.month-1)//3 == i:
                return dt.date(today.year,qmonth[i],1).strftime("%Y-%m-%d")

    if last:
        firstday = dt.datetime.strptime(get_q(first=True),"%Y-%m-%d") 
        lastday = firstday + relativedelta(months=3, days=-1)
        return lastday.strftime("%Y-%m-%d")

编辑:请让我知道这是否更适合Code Review

【问题讨论】:

  • 为什么是relativedelta(months=3) - dt.timedelta(days=1) 而不是relativedelta(months=3, days=-1)
  • @Paul - 是的,这样更好。谢谢你。我会编辑帖子。

标签: python python-2.7 datetime


【解决方案1】:

为什么要自己动手?

import pandas as pd

quarter_start = pd.to_datetime(pd.datetime.today() - pd.tseries.offsets.QuarterBegin(startingMonth=1)).date()

【讨论】:

  • 以及如何计算当前季度结束日期?
  • 将 QuarterBegin 更改为 QuarterEnd 并添加而不是减去
  • 感谢在开始月份也进行了一项更改 = 3
  • 上面的代码现在会产生这个警告:FutureWarning: The pandas.datetime class is deprecated and will be removed from pandas in a future version. Import from datetime module instead.。因此,我相信以下内容可以在不依赖 pandas.datetime 类的情况下实现相同的目标:import pandas as pd (datetime.today() - pd.tseries.offsets.QuarterBegin(startingMonth=1)).date()
【解决方案2】:

为什么这么复杂:-)

from datetime import date
from calendar import monthrange

quarter = 2
year = 2016
first_month_of_quarter = 3 * quarter - 2
last_month_of_quarter = 3 * quarter
date_of_first_day_of_quarter = date(year, first_month_of_quarter, 1)
date_of_last_day_of_quarter = date(year, last_month_of_quarter, monthrange(year, last_month_of_quarter)[1])

【讨论】:

    【解决方案3】:

    你可以这样做:

    import bisect
    import datetime as dt
    
    def get_quarter_begin():
        today = dt.date.today()
    
        qbegins = [dt.date(today.year, month, 1) for month in (1,4,7,10)]
    
        idx = bisect.bisect(qbegins, today)
        return str(qbegins[idx-1])
    

    这解决了“第一种”情况;我将“最后一个”案例留作练习,但为了清楚起见,我建议将其保留为独立函数(对于您的原始版本,如果没有传递参数,会发生什么很奇怪!)。

    【讨论】:

    • 如果你确实返回一个日期对象而不是一个字符串,它会使函数更通用。
    • @J.F.Sebastian:我完全同意——我在某种程度上模仿了 OP 所做的事情。但是,是的,删除 return 语句中的 str() 调用对我来说也更可取。
    • 不相关:对于较小的N(例如在这种情况下),可以使用线性搜索代替二分搜索:return next(date(today.year, month, 1) for month, next_month in zip(quarter_months, quarter_months[1:]) if today.month < next_month) 其中quarter_months = [1, 4, 7, 10, 13]。虽然这里的微优化可能没有意义——任何可读的解决方案都可以。
    • @JohnZwinck - 这是一个非常有趣且优雅的答案 - 感谢您的发帖。我以前从未听说过bisect 模块。我想我可以弄清楚如何完成和等效于'last' :) 只是出于兴趣,在一个函数中具有这两种功能有什么问题,如果我的函数在没有参数传递时什么也不返回,还有什么问题?
    • @Charon:一个接受参数来产生完全不同功能的函数通常不像两个独立的函数那样清晰。例如 Python 内置了 min()max(),而不是 minormax(min=False, max=False)
    【解决方案4】:
    • 避免Yo-Yo code。不要像if last 分支在问题中所做的那样,将日期对象转换为字符串只是为了立即再次将其解析回日期对象。相反,只在必要时返回一个日期对象并转换为字符串(访问对象属性,例如.year.month,比解析其字符串表示以提取相同的信息更容易)
    • 避免相互排斥的布尔关键字参数(firstlast)。它是一个容易出错的接口,并且会滋生代码重复。很容易在这里返回两个结果,稍后访问相应的属性,例如.first_day。或者如果有(不太可能的)性能问题;您可以改为创建两个函数,例如 get_first_day_of_the_quarter()
    • 为了简化算法,您可以在输入数据中添加一些冗余,例如,参见下面代码中的quarter_first_days1 在月份列表中被提及两次)——它允许无条件使用i+1
    #!/usr/bin/env python
    from collections import namedtuple
    from datetime import MINYEAR, date, timedelta
    
    DAY = timedelta(1)
    quarter_first_days = [date(MINYEAR+1, month, 1) for month in [1, 4, 7, 10, 1]]    
    Quarter = namedtuple('Quarter', 'first_day last_day')
    
    def get_current_quarter():
        today = date.today()
        i = (today.month - 1) // 3 # get quarter index
        days = quarter_first_days[i], quarter_first_days[i+1] - DAY
        return Quarter(*[day.replace(year=today.year) for day in days])
    

    MINYEAR+1 用于容纳- DAY 表达式(它假定MINYEAR < MAXYEAR)。季度指数公式来自Is there a Python function to determine which quarter of the year a date is in?

    例子:

    >>> get_current_quarter()
    Quarter(first_day=datetime.date(2016, 4, 1), last_day=datetime.date(2016, 6, 30))
    >>> str(get_current_quarter().last_day)
    '2016-06-30'
    

    【讨论】:

    • 感谢您发布此答案。我可以看到最好只使用date 对象,然后仅在必要时将它们转换为字符串。我想我会将您的答案与上述答案结合起来,以发挥我的作用。
    【解决方案5】:

    您不需要使用不必要的循环或像 pandas 这样的大型库来执行此操作。您可以使用简单的整数除法/算术和仅使用 datetime 库来完成(尽管使用 dateutil 会产生更清晰的代码)。

    import datetime
    
    def getQuarterStart(dt=datetime.date.today()):
        return datetime.date(dt.year, (dt.month - 1) // 3 * 3 + 1, 1)
    
    # using just datetime
    def getQuarterEnd1(dt=datetime.date.today()):
        nextQtYr = dt.year + (1 if dt.month>9 else 0)
        nextQtFirstMo = (dt.month - 1) // 3 * 3 + 4
        nextQtFirstMo = 1 if nextQtFirstMo==13 else nextQtFirstMo
        nextQtFirstDy = datetime.date(nextQtYr, nextQtFirstMo, 1)
        return nextQtFirstDy - datetime.timedelta(days=1)
    
    # using dateutil
    from dateutil.relativedelta import relativedelta
    
    def getQuarterEnd2(dt=datetime.date.today()):
        quarterStart = getQuarterStart(dt)
        return quarterStart + relativedelta(months=3, days=-1)
    

    输出:

    >>> d1=datetime.date(2017,2,15)
    >>> d2=datetime.date(2017,1,1)
    >>> d3=datetime.date(2017,10,1)
    >>> d4=datetime.date(2017,12,31)
    >>> 
    >>> getQuarterStart(d1)
    datetime.date(2017, 1, 1)
    >>> getQuarterStart(d2)
    datetime.date(2017, 1, 1)
    >>> getQuarterStart(d3)
    datetime.date(2017, 10, 1)
    >>> getQuarterStart(d4)
    datetime.date(2017, 10, 1)
    >>> getQuarterEnd1(d1)
    datetime.date(2017, 3, 31)
    >>> getQuarterEnd1(d2)
    datetime.date(2017, 3, 31)
    >>> getQuarterEnd1(d3)
    datetime.date(2017, 12, 31)
    >>> getQuarterEnd1(d4)
    datetime.date(2017, 12, 31)
    >>> getQuarterEnd2(d1)
    datetime.date(2017, 3, 31)
    >>> getQuarterEnd2(d2)
    datetime.date(2017, 3, 31)
    >>> getQuarterEnd2(d3)
    datetime.date(2017, 12, 31)
    >>> getQuarterEnd2(d4)
    datetime.date(2017, 12, 31)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-03
      • 2014-03-22
      • 1970-01-01
      • 2016-07-18
      相关资源
      最近更新 更多