在python中,将n个工作日添加到给定日期,忽略假日和周末

在python中,将n个工作日添加到给定日期,忽略假日和周末,python,datetime,Python,Datetime,我试图将n(整数)个工作日添加到给定日期,添加的日期必须避免节假日和周末(不包括在工作日中)这需要一些工作,因为任何库中都没有任何定义的节假日构造(至少据我所知)。您需要创建自己的枚举 通过在datetime对象上调用.weekday()=5 要添加N天,请使用以下方法: def advance(d, days): delta = datetime.timedelta(1) for x in range(days): d = d + delta

我试图将n(整数)个工作日添加到给定日期,添加的日期必须避免节假日和周末(不包括在工作日中)

这需要一些工作,因为任何库中都没有任何定义的节假日构造(至少据我所知)。您需要创建自己的枚举


通过在datetime对象上调用
.weekday()<6
可以轻松地检查周末。

没有真正的快捷方式。尝试以下方法:

  • 创建一个类,该类有一个方法
    skip(self,d)
    ,该方法返回应该跳过的日期的
    True
  • 在类中创建一个包含所有假日的字典。不要使用
    datetime
    或类似的方法,因为一天中的几分钟会让你丧命
  • 返回字典中任何日期的
    True
    ,或
    d.weekday()>=5
  • 要添加N天,请使用以下方法:

    def advance(d, days):
        delta = datetime.timedelta(1)
    
        for x in range(days):
            d = d + delta
            while holidayHelper.skip(d):
                d = d + delta
    
        return d
    

    跳过周末很容易做到以下几点:

    import datetime
    def date_by_adding_business_days(from_date, add_days):
        business_days_to_add = add_days
        current_date = from_date
        while business_days_to_add > 0:
            current_date += datetime.timedelta(days=1)
            weekday = current_date.weekday()
            if weekday >= 5: # sunday = 6
                continue
            business_days_to_add -= 1
        return current_date
    
    #demo:
    print '10 business days from today:'
    print date_by_adding_business_days(datetime.date.today(), 10)
    
    假日的问题在于,它们因国家、地区、宗教等而有很大差异。您需要为您的用例提供一个假日列表/集合,然后以类似的方式跳过它们。一个起点可能是苹果为iCal发布的日历提要(采用ics格式),而美国则是


    您可以使用该模块来解析它。

    谢谢基于omz代码,我做了一些小改动……这可能对其他用户有帮助:

    import datetime
    def date_by_adding_business_days(from_date, add_days,holidays):
        business_days_to_add = add_days
        current_date = from_date
        while business_days_to_add > 0:
            current_date += datetime.timedelta(days=1)
            weekday = current_date.weekday()
            if weekday >= 5: # sunday = 6
                continue
            if current_date in holidays:
                continue
            business_days_to_add -= 1
        return current_date
    
    #demo:
    Holidays =[datetime.datetime(2012,10,3),datetime.datetime(2012,10,4)]
    print date_by_adding_business_days(datetime.datetime(2012,10,2), 10,Holidays)
    

    如果您不介意使用第三方库,那么它很方便


    您还可以查看
    rruleset
    并使用
    .exdate()
    提供假期以跳过计算中的假期,还可以选择使用
    缓存
    选项来避免重新计算,这可能值得一看。

    我想要一个不是O(N)的解决方案,它看起来像是一个有趣的代码高尔夫。这是我写的,以防有人感兴趣。适用于正数和负数。如果我遗漏了什么,请告诉我

    def add_business_days(d, business_days_to_add):
        num_whole_weeks  = business_days_to_add / 5
        extra_days       = num_whole_weeks * 2
    
        first_weekday    = d.weekday()
        remainder_days   = business_days_to_add % 5
    
        natural_day      = first_weekday + remainder_days
        if natural_day > 4:
            if first_weekday == 5:
                extra_days += 1
            elif first_weekday != 6:
                extra_days += 2
    
        return d + timedelta(business_days_to_add + extra_days)
    

    希望这有帮助。它不是
    O(N)
    ,而是
    O(假日)
    。此外,假日仅在偏移为正时有效

    def add_working_days(start, working_days, holidays=()):
        """
        Add working_days to start start date , skipping weekends and holidays.
    
        :param start: the date to start from
        :type start: datetime.datetime|datetime.date
        :param working_days: offset in working days you want to add (can be negative)
        :type working_days: int
        :param holidays: iterator of datetime.datetime of datetime.date instances
        :type holidays: iter(datetime.date|datetime.datetime)
        :return: the new date wroking_days date from now
        :rtype: datetime.datetime
        :raise:
            ValueError if working_days < 0  and holidays 
        """
        assert isinstance(start, (datetime.date, datetime.datetime)), 'start should be a datetime instance'
        assert isinstance(working_days, int)
        if working_days < 0 and holidays:
            raise ValueError('Holidays and a negative offset is not implemented. ')
        if working_days  == 0:
            return start
        # first just add the days
        new_date = start + datetime.timedelta(working_days)
        # now compensate for the weekends.
        # the days is 2 times plus the amount of weeks are included in the offset added to the day of the week
        # from the start. This compensates for adding 1 to a friday because 4+1 // 5 = 1
        new_date += datetime.timedelta(2 * ((working_days + start.weekday()) // 5))
        # now compensate for the holidays
        # process only the relevant dates so order the list and abort the handling when the holiday is no longer
        # relevant. Check each holiday not being in a weekend, otherwise we don't mind because we skip them anyway
        # next, if a holiday is found, just add 1 to the date, using the add_working_days function to compensate for
        # weekends. Don't pass the holiday to avoid recursion more then 1 call deep.
        for hday in sorted(holidays):
            if hday < start:
                # ignore holidays before start, we don't care
                continue
            if hday.weekday() > 4:
                # skip holidays in weekends
                continue
            if hday <= new_date:
                # only work with holidays up to and including the current new_date.
                # increment using recursion to compensate for weekends
                new_date = add_working_days(new_date, 1)
            else:
                break
        return new_date
    
    def添加工作日(开始、工作日、假日=()):
    """
    将工作日添加到开始日期,跳过周末和节假日。
    :param start:起始日期
    :键入start:datetime.datetime | datetime.date
    :param working_days:要添加的工作日中的偏移量(可以为负数)
    :键入工作日:int
    :param holidays:datetime.datetime的迭代器datetime.date实例
    :类型假日:iter(datetime.date | datetime.datetime)
    :return:从现在开始的新日期
    :rtype:datetime.datetime
    :提高:
    如果工作日<0和节假日,则值错误
    """
    断言isinstance(start,(datetime.date,datetime.datetime)),'start应该是datetime实例'
    断言isinstance(工作日,整数)
    如果工作日<0和节假日:
    raise VALUE ERROR('未执行节假日和负偏移量')
    如果工作日=0:
    回程起动
    #首先加上天数
    新建日期=开始+日期时间.timedelta(工作日)
    #现在补偿周末的费用。
    #天数为2倍,加上周数,包含在添加到周中某一天的补偿中
    #从一开始。这补偿了向星期五添加1,因为4+1//5=1
    new_date+=datetime.timedelta(2*((工作日+开始.weekday())//5))
    #现在补偿假期
    #仅处理相关日期,以便在假期结束时对列表进行排序并中止处理
    #相关的。检查每个假期是否在周末,否则我们不介意,因为我们无论如何都会跳过它们
    #接下来,如果找到假日,只需在日期上添加1,使用add_working_days函数来补偿
    #周末。不要放假,以免递归超过1次调用深度。
    对于已排序的hday(假日):
    如果hday<开始:
    #在开始之前忽略假期,我们不在乎
    持续
    如果hday.weekday()大于4:
    #周末不去度假
    持续
    
    如果hday如果某人需要增加/减少天数,则扩展@omz的答案:

    def add_business_days(from_date, ndays):
        business_days_to_add = abs(ndays)
        current_date = from_date
        sign = ndays/abs(ndays)
        while business_days_to_add > 0:
            current_date += datetime.timedelta(sign * 1)
            weekday = current_date.weekday()
            if weekday >= 5: # sunday = 6
                continue
            business_days_to_add -= 1
        return current_date
    

    类似于@omz解决方案,但递归:

    def add_days_skipping_weekends(start_date, days):
        if not days:
            return start_date
        start_date += timedelta(days=1)
        if start_date.weekday() < 5:
            days -= 1
        return add_days_skipping_weekends(start_date, days)
    
    def添加天数跳过周末(开始日期,天数):
    如果不是三天:
    返回开始日期
    开始日期+=timedelta(天数=1)
    如果开始日期.工作日()小于5:
    天数-=1天
    返回添加天数跳过周末(开始日期,天)
    
    我使用以下代码处理业务日期增量。对于假期,您需要创建自己的列表以跳过

    today = datetime.now()
    t_1 = today - BDay(1)
    t_5 = today - BDay(5)
    t_1_str = datetime.strftime(t_1,"%Y%m%d")
    
    
    

    我知道它不能处理假期,但我发现这个解决方案更有用,因为它在时间上是恒定的。它包括计算整周的数量,加上假期就有点复杂了。我希望它能帮助某人:)


    你能告诉我们到目前为止你已经尝试了什么吗?还有,你希望代码如何工作?函数?或者仅仅是怎么做?你必须硬编码假期的日期(我想无论如何)你的答案是有用的,但我不能标记:(我没有足够的声望点!!谢谢!是的,我们需要的不是O(N)的东西),但这真的有可能吗?如何避免中间的假期?很棒的解决方案,尽管它不能像OP所希望的那样处理假期。此外,您应该使用
    math.floor(business\u days\u to\u add/5)
    在第二行,因此它在Python3中也可以工作。看起来可能有一个
    dateutil
    的扩展提供了这种支持:我看到一些错误。如果您从星期天开始,并添加1天。自开始。工作日()=6,所以它将增加2天,即使只需要1天。它应该是周一而不是周三。此外,当您为周末添加天数时,您不会检查是否有任何周末将被忽略
    today = datetime.now()
    t_1 = today - BDay(1)
    t_5 = today - BDay(5)
    t_1_str = datetime.strftime(t_1,"%Y%m%d")
    
    
    
    def add_days(days):
        today = datetime.date.today()
        weekday = today.weekday() + ceil(days)
        complete_weeks = weekday // 7
        added_days = weekday + complete_weeks * 2
        return today + datetime.timedelta(days=added_days)