Добавьте n рабочих дней в заданную дату, игнорируя праздники и выходные в python

Я пытаюсь добавить n (целых) рабочих дней к указанной дате, добавление даты должно избегать праздников и выходных дней (оно не включено в рабочие дни)

Ответ 1

Пропуск выходных дней будет довольно легко сделать что-то вроде этого:

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)

Проблема с праздниками заключается в том, что они сильно различаются по странам или даже по регионам, религии и т.д. Вам понадобится список/набор праздников для вашего случая использования, а затем пропустить их аналогичным образом. Отправной точкой может быть календарный канал, который Apple публикует для iCal (в формате ics), для США будет http://files.apple.com/calendars/US32Holidays.ics

Вы можете использовать модуль icalendar, чтобы проанализировать это.

Ответ 2

Если вы не против использовать стороннюю библиотеку, тогда dateutil удобен

from dateutil.rrule import *
print "In 4 business days, it's", rrule(DAILY, byweekday=(MO,TU,WE,TH,FR))[4]

Вы также можете посмотреть rruleset и использовать .exdate(), чтобы предоставить праздникам пропустить те, которые были в расчете, и, возможно, там параметр cache, чтобы избежать повторного вычисления, на которое стоит обратить внимание.

Ответ 3

Там нет реального ярлыка, чтобы сделать это. Попробуйте этот подход:

  1. Создайте класс, у которого есть метод skip(self, d) который возвращает True для дат, которые должны быть пропущены.
  2. Создайте в классе словарь, содержащий все праздники в качестве объектов даты. Не используйте datetime или подобное, потому что доли дня убьют вас.
  3. Верните 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

Ответ 4

Спасибо, на основе кода 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)

Ответ 5

Мне нужно решение, которое не было 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)

Ответ 6

Это займет некоторую работу, поскольку в любой библиотеке нет какой-либо определенной конструкции для праздников (по крайней мере, по моим сведениям). Вам нужно будет создать собственное перечисление тех.

Проверка на выходные дни выполняется легко, вызывая .weekday() < 6 в вашем объекте datetime.

Ответ 7

Надеюсь, это поможет. Это не O(N), но O(holidays). Кроме того, праздничные дни работают только тогда, когда смещение положительное.

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

Ответ 8

Если кому-то нужно добавить/вычесть дни, расширив ответ @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