【问题标题】:Python date range generator over business days工作日的 Python 日期范围生成器
【发布时间】:2020-03-07 06:28:43
【问题描述】:

我正在尝试创建一个生成器函数来迭代工作日(工作日),跳过周末(节假日也不错!)。到目前为止,我只有一个简单地迭代数天的函数:

def daterange(startDate, endDate):
    for i in xrange(int((endDate - startDate).days)):
        yield startDate + timedelta(i)

我正在努力寻找一种干净、高效且 Python 式的方法来让生成器跳过周末和节假日。提前致谢!

【问题讨论】:

标签: python


【解决方案1】:

我强烈建议使用dateutil 库来执行此类任务。一个基本的(忽略节假日的)迭代器在工作日然后就是:

from dateutil.rrule import DAILY, rrule, MO, TU, WE, TH, FR

def daterange(start_date, end_date):
  return rrule(DAILY, dtstart=start_date, until=end_date, byweekday=(MO,TU,WE,TH,FR))

【讨论】:

  • 这真的如你所说吗?我在 Linux 和 Mac OS 上的 Python 2.7 和 3.3 上尝试过它,在所有情况下它都会返回所有日期,包括周末。如果您查看dateutil.rrule.WDAYMASK,您可能会看到它是一个 0-6 的列表,即所有天,而不仅仅是周一至周五。
  • @JohnZwinck 对,WDAYMASK 确实不正确(至少对于当前版本的 dateutil)。我更新了答案以反映这一点。
  • 使用from dateutil.rrule import * 否则您将收到错误,因为 MO 未识别
  • dateutil 不支持向后迭代,以防万一您有此用例。显然作者认为这并不重要。 bugs.launchpad.net/dateutil/+bug/903925
【解决方案2】:

假设startDateendDate 是日期时间或日期对象,您可以使用the weekday method 获取星期几,如果是星期六或星期日则跳过它。做吧:

def daterange(startDate, endDate):
    for i in xrange(int((endDate - startDate).days)):
        nextDate = startDate + timedelta(i)
        if nextDate.weekday() not in (5, 6):
            yield startDate + timedelta(i)

对于假期,您必须手动检查所需的每个假期。有些假期以复杂的方式定义,所以这可能有点棘手。

【讨论】:

    【解决方案3】:

    Python pandas 有一个内置方法 bdate_range(),默认频率为工作日。 https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.bdate_range.html

    import pandas as pd
    pd.bdate_range(start='1/25/2020', end='2/24/2020')
    

    【讨论】:

      【解决方案4】:

      有一个名为dateutil 的有用库可以为您做这种事情。它可以生成日期范围(或基于自定义规则的日期),不包括某些日期,考虑从某天开始的一周等...还具有比内置日期时间库更灵活的 timedelta。

      http://labix.org/python-dateutil/ 上的文档 - 在 PyPi 上可用

      【讨论】:

        【解决方案5】:
        def get_date_range(start, end, workdays=None, holidays=None, skip_non_workdays=True):
        """
        This function calculates the durations between 2 dates skipping non workdays and holidays as specified
        :rtype : tuple
        :param start: date
        :param end: date
        :param workdays: string [Comma Separated Values, 0 - Monday through to 6 - Sunday e.g "0,1,2,3,4"]
        :param holidays: list
        :param skip_non_workdays: boolean
        :return:
        """
        from datetime import timedelta
        
        duration = 0
        
        # define workdays
        if workdays is None:
            workdays = [0, 1, 2, 3, 4]
        else:
            workdays = workdays.split(",")
        
        # check if we need to skip non workdays
        if skip_non_workdays is False:
            workdays = [0, 1, 2, 3, 4, 5, 6]
        
        # validate dates
        if end < start:
            return False, "End date is before start date"
        
        # now its time for us to iterate
        i = start
        while i <= end:
        
            # first let's give benefit of the doubt
            incr = True
        
            # lets see if day is in the workday array if not then fault it's existence here
            try:
                workdays.index(i.weekday())
            except ValueError:
                incr = False
        
            # lets check if day is an holiday, charge guilty if so.
            # We are checking the index in holiday array
            try:
                holidays.index(i)
                incr = False
            except (ValueError, AttributeError):
                pass
        
            if incr:
                duration += 1
                print "This day passed the criterion %s" % i
        
            i += timedelta(1)
        
        return True, duration
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-12-11
          • 1970-01-01
          • 2019-08-25
          • 2018-06-17
          • 1970-01-01
          • 1970-01-01
          • 2015-11-01
          • 1970-01-01
          相关资源
          最近更新 更多