【发布时间】:2011-04-06 15:13:40
【问题描述】:
如何计算两个日期之间的天数(不包括周末)?
【问题讨论】:
-
你是如何表示日期的?
-
是否还要排除公共假期?
如何计算两个日期之间的天数(不包括周末)?
【问题讨论】:
对于那些还想在不手动指定公共假期的情况下排除公共假期的人,可以使用holidays 包以及来自numpy 的busday_count。
from datetime import date
import numpy as np
import holidays
np.busday_count(
begindates=date(2021, 1, 1),
enddates=date(2021, 3, 20),
holidays=list(
holidays.US(state="CA", years=2021).keys()
),
)
【讨论】:
对于 Python 3; xrange() 仅适用于 Python 2。基于 Dave Webb 的回答,并包含显示包括周末在内的日期的代码
import datetime
start_date = datetime.date(2014, 1, 1)
end_date = datetime.date(2014, 1, 16)
delta_days = (end_date - start_date).days
delta_days # 13
day_generator = (start_date + datetime.timedelta(x + 1) for x in range((end_date - start_date).days))
delta_days = sum(1 for day in day_generator if day.weekday() < 5)
delta_days # 10
【讨论】:
这是我用于管理脚本的内容,它考虑了假期,无论您在哪个国家/地区(使用网络服务获取特定国家/地区的假期数据)。需要进行一些效率重构,但除此之外,它还有效。
from dateutil import rrule
from datetime import datetime
import pytz
timezone_manila = pytz.timezone('Asia/Manila')
class Holidays(object):
def __init__(self, holidaydata):
self.holidaydata = holidaydata
def isHoliday(self,dateobj):
for holiday in self.holidaydata:
d = datetime(holiday['date']['year'], holiday['date']['month'], holiday['date']['day'], tzinfo=timezone_manila)
if d == dateobj:
return True
return False
def pullHolidays(start, end):
import urllib.request, json
urlstring = "https://kayaposoft.com/enrico/json/v2.0/?action=getHolidaysForDateRange&fromDate=%s&toDate=%s&country=phl®ion=dc&holidayType=public_holiday" % (start.strftime("%d-%m-%Y"),end.strftime("%d-%m-%Y"))
with urllib.request.urlopen(urlstring) as url:
holidaydata = json.loads(url.read().decode())
return Holidays(holidaydata)
def countWorkDays(start, end):
workdays=0
holidayData=pullHolidays(start,end)
for dt in rrule.rrule(rrule.DAILY, dtstart=start, until=end):
if dt.weekday() < 5:
if holidayData.isHoliday(dt) == False:
workdays+=1
return workdays
【讨论】:
到目前为止,我发现所提供的解决方案都不令人满意。要么存在对我不想要的库的依赖,要么存在低效的循环算法,或者存在不适用于所有情况的算法。不幸的是,@neil 提供的那个不能很好地工作。 @vekerdyb 的回答更正了这一点,不幸的是,这也不适用于所有情况(例如,在同一个周末选择周六或周日......)。
所以我坐下来,尽力想出一个适用于所有输入日期的解决方案。它小巧高效。当然,您也可以随意发现其中的错误。开始和结束都包含在内(例如,一周中的周一至周二为 2 个工作日)。
def get_workdays(from_date: datetime, to_date: datetime):
# if the start date is on a weekend, forward the date to next Monday
if from_date.weekday() > 4:
from_date = from_date + timedelta(days=7 - from_date.weekday())
# if the end date is on a weekend, rewind the date to the previous Friday
if to_date.weekday() > 4:
to_date = to_date - timedelta(days=to_date.weekday() - 4)
if from_date > to_date:
return 0
# that makes the difference easy, no remainders etc
diff_days = (to_date - from_date).days + 1
weeks = int(diff_days / 7)
return weeks * 5 + (to_date.weekday() - from_date.weekday()) + 1
【讨论】:
首先将numpy 导入为np。函数np.busday_count计算两个日期之间的有效天数,不包括结束日期的那一天。
如果结束日期早于开始日期,则计数将为负数。有关np.busday_count 的更多信息,请阅读文档here。
import numpy as np
np.busday_count('2018-04-10', '2018-04-11')
请注意,该函数接受字符串,在调用该函数之前无需实例化datetime 对象。
还支持特定的有效日期以及添加假期的选项。
import numpy as np
np.busyday_count('2019-01-21','2020-03-28',weekmask=[1,1,1,1,1,0,0],holidays=['2020-01-01'])
weekmask 格式 = [Mon,Tue,Wed....Sat,Sun]
【讨论】:
holidays:pypi.org/project/holidays
您可以使用https://pypi.org/project/python-networkdays/ 该包没有依赖项,没有 NumPy 或 pandas 来计算日期。 ;)
In [3]: import datetime
In [4]: from networkdays import networkdays
In [5]: HOLIDAYS = { datetime.date(2020, 12, 25),}
In [6]: days = networkdays.Networkdays(datetime.date(2020, 12, 1),datetime.date(2020, 12, 31), holidays=HOLIDAYS, weekdaysoff={6,7})
In [7]: days.networkdays()
Out[7]:
[datetime.date(2020, 12, 1),
datetime.date(2020, 12, 2),
datetime.date(2020, 12, 3),
datetime.date(2020, 12, 4),
datetime.date(2020, 12, 7),
datetime.date(2020, 12, 8),
datetime.date(2020, 12, 9),
datetime.date(2020, 12, 10),
datetime.date(2020, 12, 11),
datetime.date(2020, 12, 14),
datetime.date(2020, 12, 15),
datetime.date(2020, 12, 16),
datetime.date(2020, 12, 17),
datetime.date(2020, 12, 18),
datetime.date(2020, 12, 21),
datetime.date(2020, 12, 22),
datetime.date(2020, 12, 23),
datetime.date(2020, 12, 24),
datetime.date(2020, 12, 28),
datetime.date(2020, 12, 29),
datetime.date(2020, 12, 30),
datetime.date(2020, 12, 31)]
【讨论】:
您可以使用以下万无一失的函数来获取任意两个给定日期之间的工作日数:
import datetime
def working_days(start_dt,end_dt):
num_days = (end_dt -start_dt).days +1
num_weeks =(num_days)//7
a=0
#condition 1
if end_dt.strftime('%a')=='Sat':
if start_dt.strftime('%a') != 'Sun':
a= 1
#condition 2
if start_dt.strftime('%a')=='Sun':
if end_dt.strftime('%a') !='Sat':
a =1
#condition 3
if end_dt.strftime('%a')=='Sun':
if start_dt.strftime('%a') not in ('Mon','Sun'):
a =2
#condition 4
if start_dt.weekday() not in (0,6):
if (start_dt.weekday() -end_dt.weekday()) >=2:
a =2
working_days =num_days -(num_weeks*2)-a
return working_days
使用示例:
start_dt = datetime.date(2019,6,5)
end_dt = datetime.date(2019,6,21)
working_days(start_dt,end_dt)
在这里,开始日期和结束日期都包括在内,不包括所有周末。
希望这会有所帮助!
【讨论】:
我的解决方案也是计算最后一天。因此,如果 start 和 end 设置为同一工作日,则 asnwer 将为 1(例如 10 月 17 日)。 如果开始和结束是连续 2 个工作日,则答案将为 2(例如 10 月 17 日和 18 日)。 它计算整个星期(每个星期我们将有 2 个周末),然后检查提醒天是否包含周末。
import datetime
def getWeekdaysNumber(start,end):
numberOfDays = (end-start).days+1
numberOfWeeks = numberOfDays // 7
reminderDays = numberOfDays % 7
numberOfDays -= numberOfWeeks *2
if reminderDays:
#this line is creating a set of weekdays for remainder days where 7 and 0 will be Saturday, 6 and -1 will be Sunday
weekdays = set(range(end.isoweekday(), end.isoweekday() - reminderDays, -1))
numberOfDays -= len(weekdays.intersection([7,6,0,-1])
return numberOfDays
用法示例:
start = date(2018,10,10)
end = date (2018,10,17)
result = getWeekdaysNumber(start,end)`
【讨论】:
请注意,@neil 的(否则很棒)代码将在周日至周四的间隔内失败。这是一个修复:
def working_days_in_range(from_date, to_date):
from_weekday = from_date.weekday()
to_weekday = to_date.weekday()
# If start date is after Friday, modify it to Monday
if from_weekday > 4:
from_weekday = 0
day_diff = to_weekday - from_weekday
whole_weeks = ((to_date - from_date).days - day_diff) / 7
workdays_in_whole_weeks = whole_weeks * 5
beginning_end_correction = min(day_diff, 5) - (max(to_weekday - 4, 0) % 5)
working_days = workdays_in_whole_weeks + beginning_end_correction
# Final sanity check (i.e. if the entire range is weekends)
return max(0, working_days)
【讨论】:
在 PyPi 中使用这个名为 business-duration 的包。
from business_duration import businessDuration
import pandas as pd
import datetime
start = pd.to_datetime("2010-1-1 00:00:00")
end = pd.to_datetime("2010-3-31 00:00:00")
businessDuration(startdate=start,enddate=end,unit='day')
输出[6]:62.99927083333333
【讨论】:
这是我实现的一个函数,用于测量跨分支集成代码需要多少工作日。它不需要像其他解决方案那样在整个中间天进行迭代,而只需要在第一周进行。
这个问题可以分解成两个不同的问题:
计算区间内的整数周数:对于整数周,周末天数始终为 2。这是一个微不足道的整数除法:(todate - fromdate)/7
计算剩余区间的周末天数:这可以通过计数方法(类似map-reduce)轻松解决:sum(map(is_weekend, rem_days))。
def count_working_days(fromdate, todate):
from datetime import timedelta as td
def is_weekend(d): return d.weekday() > 4
# 1st problem
num_weeks = (todate - fromdate).days/7
# 2nd problem
rem_days = (todate - fromdate).days%7
rem_weekend_days = sum(is_weekend(fromdate + td(days=i+1)) for i in range(rem_days))
return (todate - fromdate).days - 2*num_weeks - rem_weekend_days
及其工作示例:
>>> for i in range(10): latency(datetime.now(), datetime.now() + timedelta(days=i))
...
0 1 1 1 2 3 4 5 6 6
【讨论】:
到目前为止给出的答案都有效,但如果日期相距很远(由于循环),效率会非常低。
这应该可行:
import datetime
start = datetime.date(2010,1,1)
end = datetime.date(2010,3,31)
daydiff = end.weekday() - start.weekday()
days = ((end-start).days - daydiff) / 7 * 5 + min(daydiff,5) - (max(end.weekday() - 4, 0) % 5)
这会将其转换为整周(有 5 个工作日),然后处理剩余的天数。
【讨论】:
我认为最干净的解决方案是使用 numpy 函数busday_count
import numpy as np
import datetime as dt
start = dt.date( 2014, 1, 1 )
end = dt.date( 2014, 1, 16 )
days = np.busday_count( start, end )
【讨论】:
懒惰的方法是pip install workdays 获取执行此操作的python 包。
【讨论】:
我将 Dave Webb 的答案改编成一个函数并添加了一些测试用例:
import datetime
def weekdays_between(start, end):
return sum([1 for daydelta in xrange(1, (end - start).days + 1)
if (start + datetime.timedelta(daydelta)).weekday() < 5])
assert 7 == weekdays_between(
datetime.date(2014,2,19),
datetime.date(2014,3,1))
assert 1 == weekdays_between(
datetime.date(2014,2,19),
datetime.date(2014,2,20))
assert 2 == weekdays_between(
datetime.date(2014,2,19),
datetime.date(2014,2,22))
assert 2 == weekdays_between(
datetime.date(2014,2,19),
datetime.date(2014,2,23))
assert 3 == weekdays_between(
datetime.date(2014,2,19),
datetime.date(2014,2,24))
assert 1 == weekdays_between(
datetime.date(2014,2,21),
datetime.date(2014,2,24))
assert 1 == weekdays_between(
datetime.date(2014,2,22),
datetime.date(2014,2,24))
assert 2 == weekdays_between(
datetime.date(2014,2,23),
datetime.date(2014,2,25))
【讨论】:
我尝试了前两个答案(Dave Webb 和 Neil 的),但由于某种原因,我从两个答案中都得到了不正确的答案。这可能是我的一个错误,但我选择了一个现有的库,因为它可能具有更多功能并且针对边缘情况进行了更好的测试:
【讨论】:
固定周六至周日在同一周末正常运行。
from __future__ import print_function
from datetime import date, timedelta
def workdaycount(startdate,enddate):
if startdate.year != enddate.year:
raise ValueError("Dates to workdaycount must be during same year")
if startdate == enddate:
return int(startdate.weekday() < 5)
elif (enddate - startdate).days == 1 and enddate.weekday() == 6: # Saturday and Sunday same weekend
return 0
first_week_workdays = min(startdate.weekday(), 4) + 1
last_week_workdays = min(enddate.weekday(), 4) + 1
workweeks = int(enddate.strftime('%W')) - int(startdate.strftime('%W'))
return (5 * workweeks) + last_week_workdays - first_week_workdays + 1
for comment, start,end in (
("Two dates same weekend:", date(2010,9,18), date(2010,9,19)),
("Same dates during weekend:", date(2010,9,19), date(2010,9,19)),
("Same dates during week", date(2010,9,16), date(2010,9,16)),
("Dates during same week", date(2010,9,13), date(2010,9,16)),
("Dates during following weeks", date(2010,9,7), date(2010,9,16)),
("Dates after two weeks", date(2010,9,7), date(2010,9,24)),
("Dates from other solution", date(2010,1, 1), date(2010, 3,31))):
daydiff = end.weekday() - start.weekday()
days = ((end-start).days - daydiff) / 7 * 5 + min(daydiff,5)
daygenerator = (start + timedelta(x + 1) for x in xrange((end - start).days))
gendays = sum(day.weekday() < 5 for day in daygenerator)
print(comment,start,end,workdaycount(start,end))
print('Other formula:', days, '. Generator formula: ', gendays)
【讨论】:
>>> from datetime import date,timedelta
>>> fromdate = date(2010,1,1)
>>> todate = date(2010,3,31)
>>> daygenerator = (fromdate + timedelta(x + 1) for x in xrange((todate - fromdate).days))
>>> sum(1 for day in daygenerator if day.weekday() < 5)
63
这个creates a generator using a generator expression 哪个will yield the list of days 从fromdate 到todate。
然后我们可以从生成器创建一个列表,使用the weekday() function 过滤掉周末,列表的大小给出了我们想要的天数。但是,为了将整个列表保存在内存中,如果日期相隔很长时间,这可能会成为问题,我们使用另一个生成器表达式,它过滤掉周末但返回 1 而不是每个日期。 We can then just add all these 1s together to get the length 无需存储整个列表。
注意,如果fromdate == todate 这计算的是 0 而不是 1。
【讨论】:
todate。只需将 xrange 表达式更改为 xrange((todate-fromdate).days + 1),这将为您提供包含 todate 的日生成器。另外,顺便说一句,但由于 True = 1,您的 sum 表达式可以简单地读取(如果您关心的话)sum(day.weekday() < 5 for day in daygenerator)。也许有点聪明,但这不是一个糟糕的习惯用法,用于计算符合条件的事物的数量。
import datetime
# some givens
dateB = datetime.date(2010, 8, 31)
dateA = datetime.date(2010, 7, 8)
delta = datetime.timedelta(1)
# number of days
days = 0
while dateB != dateA:
#subtract a day
dateB -= delta
# if not saturday or sunday, add to count
if dateB.isoweekday() not in (6, 7):
days += 1
我认为这样的事情应该可行。我现在没有测试它的工具。
【讨论】: