Add n business days to a given date ignoring holidays and weekends in python

前端 未结 11 628
天涯浪人
天涯浪人 2020-12-05 10:32

I\'m trying to add n (integer) working days to a given date, the date addition has to avoid the holidays and weekends (it\'s not included in the working days)

相关标签:
11条回答
  • 2020-12-05 11:11

    If someone needs to add/substract days, extending @omz's answer:

    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
    
    0 讨论(0)
  • 2020-12-05 11:11

    similar to @omz solution but recursively:

    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)
    
    0 讨论(0)
  • 2020-12-05 11:14

    I wanted a solution that wasn't O(N) and it looked like a fun bit of code golf. Here's what I banged out in case anyone's interested. Works for positive and negative numbers. Let me know if I missed anything.

    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)
    
    0 讨论(0)
  • 2020-12-05 11:19

    Hope this helps. It's not O(N) but O(holidays). Also, holidays only works when the offset is positive.

    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
    
    0 讨论(0)
  • 2020-12-05 11:20

    Thanks based on omz code i made some little changes ...it maybe helpful for other users:

    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)
    
    0 讨论(0)
提交回复
热议问题