29

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

4

12 回答 12

38

跳过周末会很容易做这样的事情:

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模块来解析它。

于 2012-10-02T14:13:56.367 回答
17

如果您不介意使用 3rd 方库,那么dateutil很方便

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

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

于 2012-10-02T15:47:41.393 回答
7

没有真正的捷径可以做到这一点。试试这个方法:

  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
于 2012-10-02T14:00:17.903 回答
7

感谢基于 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)
于 2012-10-02T14:42:37.163 回答
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)
于 2014-04-28T23:18:03.847 回答
1

这将需要一些工作,因为在任何图书馆中都没有任何定义的假期结构(至少据我所知)。您将需要创建自己的枚举。

.weekday() < 6通过调用您的 datetime 对象可以轻松地检查周末天数。

于 2012-10-02T13:57:24.097 回答
1

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

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)
于 2020-11-30T09:25:04.983 回答
0

希望这可以帮助。不是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
于 2016-06-07T14:59:46.217 回答
0

如果有人需要添加/减去天数,请扩展@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
于 2019-02-02T01:00:46.197 回答
0

类似于@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)
于 2019-12-05T14:25:23.477 回答
0

如果您对使用 NumPy 感兴趣,那么您可以按照以下解决方案进行操作:

import numpy as np
from datetime import datetime, timedelta

def get_future_date_excluding_weekends(date,no_of_days):
 """This methods return future date by adding given number of days excluding 
  weekends"""
  future_date = date + timedelta(no_of_days)
  no_of_busy_days = int(np.busday_count(date.date(),future_date.date()))
  if no_of_busy_days != no_of_days:
    extend_future_date_by = no_of_days - no_of_busy_days
    future_date = future_date + timedelta(extend_future_date_by)
  return future_date
于 2021-08-27T05:43:50.520 回答
-1

我正在使用以下代码来处理业务日期增量。对于假期,您需要创建自己的列表以跳过。

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


于 2020-06-25T02:32:07.083 回答