将 n 个工作日添加到给定日期,忽略 python 中的假期和周末
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12691551/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Add n business days to a given date ignoring holidays and weekends in python
提问by cyberbrain
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)
我正在尝试将 n(整数)个工作日添加到给定日期,日期添加必须避免假期和周末(不包括在工作日中)
采纳答案by omz
Skipping weekends would be pretty easy doing something like this:
跳过周末会很容易做这样的事情:
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)
The problem with holidays is that they vary a lot by country or even by region, religion, etc. You would need a list/set of holidays for your use case and then skip them in a similar way. A starting point may be the calendar feed that Apple publishes for iCal (in the ics format), the one for the US would be http://files.apple.com/calendars/US32Holidays.ics
假期的问题在于它们因国家或什至因地区、宗教等而异。您需要为您的用例列出/一组假期,然后以类似的方式跳过它们。一个起点可能是 Apple 为 iCal 发布的日历提要(采用 ics 格式),美国的一个是http://files.apple.com/calendars/US32Holidays.ics
You could use the icalendarmodule to parse this.
您可以使用icalendar模块来解析它。
回答by Tim Lamballais
This will take some work since there isn't any defined construct for holidays in any library (by my knowledge at least). You will need to create your own enumeration of those.
这将需要一些工作,因为在任何图书馆都没有任何定义的假期构造(至少据我所知)。您将需要创建自己的枚举。
Checking for weekend days is done easily by calling .weekday() < 6on your datetime object.
通过调用.weekday() < 6datetime 对象可以轻松检查周末天数。
回答by Aaron Digulla
There is no real shortcut to do this. Try this approach:
没有真正的捷径可以做到这一点。试试这个方法:
- Create a class which has a method
skip(self, d)which returnsTruefor dates that should be skipped. - Create a dictionary in the class which contains all holidays as date objects. Don't use
datetimeor similar because the fractions of a day will kill you. - Return
Truefor any date that is in the dictionary ord.weekday() >= 5
- 创建一个类,该类具有
skip(self, d)返回True应跳过的日期的方法。 - 在包含所有假期作为日期对象的类中创建一个字典。不要使用
datetime或类似的,因为一天的一小部分会杀死你。 - 返回
True字典中的任何日期或d.weekday() >= 5
To add N days, use this method:
要添加 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
回答by cyberbrain
Thanks based on omz code i made some little changes ...it maybe helpful for other users:
感谢基于 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)
回答by Jon Clements
If you don't mind using a 3rd party library then dateutilis handy
如果您不介意使用 3rd 方库,那么dateutil很方便
from dateutil.rrule import *
print "In 4 business days, it's", rrule(DAILY, byweekday=(MO,TU,WE,TH,FR))[4]
You can also look at rrulesetand using .exdate()to provide the holidays to skip those in the calculation, and optionally there's a cacheoption to avoid re-calculating that might be worth looking in to.
您还可以查看rruleset并使用.exdate()提供假期以跳过计算中的假期,并且可以cache选择避免重新计算可能值得研究的选项。
回答by royal
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.
我想要一个不是 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)
回答by Remco
Hope this helps. It's not O(N)but O(holidays). Also, holidays only works when the offset is positive.
希望这可以帮助。不是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
回答by arod
If someone needs to add/substract days, extending @omz's answer:
如果有人需要添加/减去天数,请扩展@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
回答by Adam
similar to @omz solution but recursively:
类似于@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)

