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
I think something like that should work. I don't have the tools to test it right now.
>>> 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
We could then create a list from the generator, filtering out weekends using the weekday() function, and the size of the list gives the number of days we want. However, to save having the whole list in memory which could be a problem if the dates are a long time apart we use another generator expression which filters out weekends but returns 1 instead of each date. We can then just add all these 1s together to get the length without having to store the whole list.
Note, if fromdate == todate this calculate 0 not 1.
I tried the top two answers (Dave Webb's and neil's) and for some reason I was getting incorrect answers from both. It might have been an error on my part but I went with an existing library on the basis that it probably had more functionality and was better tested for edge cases:
This is a function that I implemented for measuring how many working days it takes for code to be integrated across branches. It does not need iterating over the whole intermediate days, as other solutions do, but only for the first week.
This problem can be broken down into two different problems:
Calculating the number of integral weeks in the interval: for an integral week, the number of weekend days is always 2. This is a trivial integer division: (todate - fromdate)/7
Calculating the number of weekend days in the remaining interval: this can be easily solved with the counting approach (map-reduce like): 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
And a sample of its working:
>>> for i in range(10): latency(datetime.now(), datetime.now() + timedelta(days=i))
...
0 1 1 1 2 3 4 5 6 6
My solution is also counting the last day. So if start and end are set to the same weekday then the asnwer will be 1 (eg 17th Oct both).
If start and end are 2 consecutive weekdays then answer will be 2 (eg for 17th and 18th Oct).
It counts the whole weeks (in each we will have 2 weekend days) and then check reminder days if they contain weekend days.
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
usage example:
start = date(2018,10,10)
end = date (2018,10,17)
result = getWeekdaysNumber(start,end)`
You can use the following foolproof function to get the number of working days between any two given dates:
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
So far, I found none of the provided solutions satisfactory. Either there is a dependency to a lib I don't want or there are inefficient looping algorithms or there are algorithms that won't work for all cases. Unfortunately the one provided by @neil did not work sufficiently well. This was corrected by @vekerdyb's answer which unfortunately did not work for all cases, either (pick a Saturday or a Sunday on the same weekend for example...).
So I sat down and tried my best to come up with a solution that is working for all dates entered. It's small and efficient. Feel free to find errors in this one, as well, of course. Beginning and end are inclusive (so Monday-Tuesday in one week are 2 workdays for example).
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
Here's something I use for my management scripts, which takes into account holidays, regardless of which country you're in (uses a web service to pull in country-specific holiday data). Needs a bit of efficiency refactoring but besides that, it works.
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
For those also wanting to exclude public holidays without manually specifying them, one can use the holidays package along with busday_count from numpy.
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()
),
)
2. Simple math: without usage of service for public holidays/extra work days
Even if @Sebastian answer can't be applied in many cases, as it's not considering public holidays and extra working days, I still find it great, as it's do the job without and decided to fix a bug (basically only his last line was changed).
from datetime import date, timedelta
WEEKDAY_FRIDAY = 4 # date.weekday() starts with 0
def count_work_days(start_date: date, end_date: date):
"""
Math function to get workdays between 2 dates.
Can be used only as fallback as it doesn't know
about specific country holidays or extra working days.
"""
# if the start date is on a weekend, forward the date to next Monday
if start_date.weekday() > WEEKDAY_FRIDAY:
start_date = start_date + timedelta(days=7 - start_date.weekday())
# if the end date is on a weekend, rewind the date to the previous Friday
if end_date.weekday() > WEEKDAY_FRIDAY:
end_date = end_date - timedelta(days=end_date.weekday() - WEEKDAY_FRIDAY)
if start_date > end_date:
return 0
# that makes the difference easy, no remainders etc
diff_days = (end_date - start_date).days + 1
weeks = int(diff_days / 7)
remainder = end_date.weekday() - start_date.weekday() + 1
if remainder != 0 and end_date.weekday() < start_date.weekday():
remainder = 5 + remainder
return weeks * 5 + remainder
def test(test_name: str, start_date: date, end_date: date, expected: int):
print(f"Running: {test_name}... ", end="")
params = dict(
start_date=start_date,
end_date=end_date,
)
assert expected == count_work_days(**params), dict(
expected=expected,
actual=count_work_days(**params),
**params
)
print("ok")
# Start on Mon
test("Mon - Mon", date(2022, 4, 4), date(2022, 4, 4), 1)
test("Mon - Tue", date(2022, 4, 4), date(2022, 4, 5), 2)
test("Mon - Wed", date(2022, 4, 4), date(2022, 4, 6), 3)
test("Mon - Thu", date(2022, 4, 4), date(2022, 4, 7), 4)
test("Mon - Fri", date(2022, 4, 4), date(2022, 4, 8), 5)
test("Mon - Sut", date(2022, 4, 4), date(2022, 4, 9), 5)
test("Mon - Sun", date(2022, 4, 4), date(2022, 4, 10), 5)
test("Mon - next Mon", date(2022, 4, 4), date(2022, 4, 11), 6)
test("Mon - next Tue", date(2022, 4, 4), date(2022, 4, 12), 7)
# Start on Fri
test("Fri - Sut", date(2022, 4, 1), date(2022, 4, 2), 1)
test("Fri - Sun", date(2022, 4, 1), date(2022, 4, 3), 1)
test("Fri - Mon", date(2022, 4, 1), date(2022, 4, 4), 2)
test("Fri - Tue", date(2022, 4, 1), date(2022, 4, 5), 3)
test("Fri - Wed", date(2022, 4, 1), date(2022, 4, 6), 4)
test("Fri - Thu", date(2022, 4, 1), date(2022, 4, 7), 5)
test("Fri - next Fri", date(2022, 4, 1), date(2022, 4, 8), 6)
test("Fri - next Sut", date(2022, 4, 1), date(2022, 4, 9), 6)
test("Fri - next Sun", date(2022, 4, 1), date(2022, 4, 10), 6)
test("Fri - next Mon", date(2022, 4, 1), date(2022, 4, 11), 7)
# Some edge cases
test("start > end", date(2022, 4, 2), date(2022, 4, 1), 0)
test("Sut - Sun", date(2022, 4, 2), date(2022, 4, 3), 0)
test("Sut - Mon", date(2022, 4, 2), date(2022, 4, 4), 1)
test("Sut - Fri", date(2022, 4, 2), date(2022, 4, 8), 5)
test("Thu - Fri", date(2022, 3, 31), date(2022, 4, 8), 7)