86

How can I calculate number of days between two dates excluding weekends?

DevLounge
  • 8,313
  • 3
  • 31
  • 44
Mohammad Nadeem
  • 897
  • 1
  • 7
  • 4

22 Answers22

122

I think the cleanest solution is to use the numpy function 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 )
Ben
  • 1,638
  • 2
  • 14
  • 21
67
>>> 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

This creates a generator using a generator expression which will yield the list of days to get from the fromdate to todate.

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.

Community
  • 1
  • 1
David Webb
  • 190,537
  • 57
  • 313
  • 299
  • 6
    Your note indicates that this counts up to but not including `todate`. Just change the xrange expression to `xrange((todate-fromdate).days + 1)`, and that will give you a daygenerator inclusive of `todate`. Also, just an aside, but since True = 1, your sum expression can simply read (if you care), `sum(day.weekday() < 5 for day in daygenerator)`. Maybe a bit clever, but this not a terrible idiom for counting the number of things that match a condition. – PaulMcG Sep 01 '10 at 09:25
  • 2
    This is a *great* use for a generator expression! – rickcnagy Sep 19 '14 at 04:30
30

First import numpy as np. The function np.busday_count counts the number of valid days between two dates, excluding the day of the end date.

If end date is earlier than the begin date, the count will be negative. For more on np.busday_count read the documentation here.

import numpy as np
np.busday_count('2018-04-10', '2018-04-11')

Note that the function accepts strings, it's not necessary to instantiate datetime objects before calling the function.

Also supports specific valid days and option to add holidays as well.

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 format = [Mon,Tue,Wed.....Sat,Sun]

user5722540
  • 590
  • 8
  • 24
Yinan
  • 309
  • 3
  • 3
  • 4
    congratulations to your first post. unfortunately this answer was already provided by the user Ben down below about 3 1/2 years ago. Please read other answers before providing one by yourself to avoid such duplicates – Kristianmitk Apr 10 '18 at 12:26
  • Make sure your dates are converted to strings if you're pulling your data from a series. Example df['mydatecol'].dt.strftime('%Y-%m-%d'). – Arthur D. Howland Dec 04 '19 at 18:08
  • For the lazy, use `holidays`: https://pypi.org/project/holidays/ – ijoseph Nov 07 '21 at 20:27
10

The lazy way is to pip install workdays to get the python package that does exactly this.

https://pypi.python.org/pypi/workdays/

Jody
  • 8,021
  • 4
  • 26
  • 29
Matthew Scouten
  • 15,303
  • 9
  • 33
  • 50
  • I am using this package, however it is lacking in other features such as returning the a list of individual dates – tread Jan 02 '18 at 18:50
  • https://github.com/seatgeek/businesstime works well if you want to get days+hours in timedelta form. – hnasarat Sep 26 '19 at 01:05
10

The answers given so far will work, but are highly inefficient if the dates are a large distance apart (due to the loop).

This should work:

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)

This turns it into whole weeks (which have 5 working days) and then deals with the remaining days.

mistyrouge
  • 219
  • 1
  • 7
neil
  • 3,387
  • 1
  • 14
  • 11
  • b-a undefined, should be end-start? – Tony Veijalainen Sep 01 '10 at 20:08
  • Good point - I wrote it with a and b and then changed to meaningful names. I see you have worked on the edge cases that I may have missed. I may revisit this if I have time and check it is watertight. – neil Sep 03 '10 at 09:40
  • 3
    Note that the @neil's code will fail for Sunday-Thursday intervals. Here is a fix http://stackoverflow.com/a/32698089/1617748 (edit: new answer and link for better formatting) – vekerdyb Sep 21 '15 at 14:39
  • @vekerdyb Unfortunately yours isn't working for all cases, either. See my answer https://stackoverflow.com/a/62835010/865695. – Sebastian Jul 10 '20 at 13:10
  • This fails for quite a few test cases. When compared to `numpy` the following cases fail `(datetime(1800, 4, 6), datetime(2023, 4, 16)), (datetime(1800, 4, 6), datetime(2023, 4, 15)), (datetime(2023, 4, 8), datetime(2023, 4, 16)), (datetime(2023, 4, 8), datetime(2023, 4, 15)), (datetime(2023, 4, 9), datetime(2023, 4, 15)), (datetime(2023, 4, 9), datetime(2023, 4, 16))` – Josmoor98 Apr 15 '23 at 12:00
7

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
Sebastian
  • 5,177
  • 4
  • 30
  • 47
  • I was reading your response for carrying out an exercise like this. If I would like to get the days (Mon-Fri) but excluding the last days, how should be this code reshape, if something needs to be changed? – 12666727b9 Mar 28 '22 at 10:00
  • error in the last line: `to_date.weekday() - from_date.weekday()` you can get negative value, so period Fri-Mon will give -3, instead of 2. Easy fix: `if to_date.weekday() < from_date.weekday(): remainder = 5 + remainder` where `remainder = (to_date.weekday() - from_date.weekday()) + 1` – vvkuznetsov Apr 21 '22 at 19:49
  • actually there was a need to one more fix, I've added my version in https://stackoverflow.com/a/71977946/1654692 – vvkuznetsov Apr 23 '22 at 08:12
  • it fails for 2023-01-24 2023-04-17 – Alex M.M. Aug 02 '22 at 16:49
4

1. With using external service to get public holidays/extra work days

Can't thank @Renan enough for introducing this amazing API that I'm now using in my own project. Here's his answer with slight cleaning + tests.

import urllib.request
import json
from typing import Dict

from dateutil import rrule
from datetime import date


WEEKDAY_FRIDAY = 4  # date.weekday() starts with 0


class CountryCalendar(object):

    def __init__(self, special_dates: Dict[date, str]):
        self.special_dates = special_dates

    def is_working_day(self, dt: date):
        date_type = self.special_dates.get(dt)
        if date_type == "extra_working_day":
            return True
        if date_type == "public_holiday":
            return False
        return dt.weekday() <= WEEKDAY_FRIDAY


def load_calendar(
    country: str,
    region: str,
    start_date: date,
    end_date: date
) -> CountryCalendar:
    """
    Access Enrico Service 2.0 JSON
    https://kayaposoft.com/enrico/

    Response format (for country=rus):

    [
        {
            holidayType: "public_holiday",
            date: {
                day: 2,
                month: 1,
                year: 2022,
                dayOfWeek: 7
            },
            name: [
                {lang: "ru", text: "Новогодние каникулы"},
                {lang: "en", text: "New Year’s Holiday"}
            ]
        },
        ...
    ]
    """
    urlstring = (
        f"https://kayaposoft.com/enrico/json/v2.0/"
        f"?action=getHolidaysForDateRange"
        f"&fromDate={start_date:%d-%m-%Y}"
        f"&toDate={end_date:%d-%m-%Y}"
        f"&country={country}"
        f"&region={region}"
        f"&holidayType=all"
    )

    with urllib.request.urlopen(urlstring) as url:
        payload = json.loads(url.read().decode())

    return CountryCalendar({
        date(
            special_date["date"]["year"],
            special_date["date"]["month"],
            special_date["date"]["day"],
        ): special_date["holidayType"]
        for special_date in payload
    })


def count_work_days(
    start_date: date,
    end_date: date,
    country: str,
    region: str,
):
    """
    Get working days specific for country
    using public holidays internet provider
    """
    if start_date > end_date:
        return 0

    try:
        country_calendar = load_calendar(country, region, start_date, end_date)
    except Exception as exc:
        print(f"Error accessing calendar of country: {country}. Exception: {exc}")
        raise

    workdays = 0
    for dt in rrule.rrule(rrule.DAILY, dtstart=start_date, until=end_date):
        if country_calendar.is_working_day(dt):
            workdays += 1

    return workdays


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,
        country="rus",
        region=""
    )
    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)

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)
vvkuznetsov
  • 1,056
  • 1
  • 9
  • 16
3

I adapted Dave Webb's answer into a function and added some test cases:

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))
jterrace
  • 64,866
  • 22
  • 157
  • 202
3

Note that the @neil's (otherwise great) code will fail for Sunday-Thursday intervals. Here is a fix:

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)
Community
  • 1
  • 1
vekerdyb
  • 1,213
  • 12
  • 26
3
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.

Coding District
  • 11,901
  • 4
  • 26
  • 30
3

Fixed Saturday to Sunday same weekend to function.

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)
Tony Veijalainen
  • 5,447
  • 23
  • 31
3

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

Usage example:

start_dt = datetime.date(2019,6,5)
end_dt = datetime.date(2019,6,21)

working_days(start_dt,end_dt)

Here, both the start date and the end date is included, excluding all the weekends.
Hope this helps!!

jitesh2796
  • 164
  • 6
  • 14
  • What advantage does this approach have over all the other answers already given? – Sneftel Jun 21 '19 at 13:21
  • This approach is quite efficient and foolproof. It takes into consideration all the possible scenarios and has minimal run time as it doesn't require any loop. Try it out!! – jitesh2796 Jun 21 '19 at 18:20
  • It's fool-proof until Christmas falls on a Saturday and Monday becomes a non-working day. – JeffUK Dec 17 '20 at 15:06
  • haha @JeffUK yeah that kinda breaks the spell! – jitesh2796 Dec 17 '20 at 18:38
  • @Sneftel the "advantage", is that some of these answers are already incorrect in my tests. This one too. Fails for 2023-01-24 - 2023-04-17 Should return 59, not 61 – Alex M.M. Aug 02 '22 at 16:51
  • @AlexM.M. my solution is returning 60 which seems to be right as per the calculation. I'm counting both start and end date if they fall on weekdays. It seems to me that you not counting the end date and hence expecting 59. You can simply do -1 if that's the case – jitesh2796 Aug 09 '22 at 08:32
3

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&region=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
Renan
  • 827
  • 8
  • 16
  • 1
    thanks, for the nice API, 1. I personally went with `holidayType=all` to get extra working day - which is common in our country when we work on Sartuday to get additional holiday during work week. 2. I confused with your use of timezone and even `datetime`, as this will work simpler with `date` without need for timezone. – vvkuznetsov Apr 22 '22 at 17:49
  • thanks again, I've added my version in case it's helpful to anyone https://stackoverflow.com/a/71977946/1654692 – vvkuznetsov Apr 23 '22 at 08:11
0

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:

https://bitbucket.org/shelldweller/python-bizdatetime

Andy Baker
  • 21,158
  • 12
  • 58
  • 71
0

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:

  1. 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

  2. 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
0

Use this package called business-duration in PyPi.

Example Code:

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')

Out[6]: 62.99927083333333

Kristianmitk
  • 4,528
  • 5
  • 26
  • 46
Gnaneshwar G
  • 111
  • 1
  • 4
0

you may use https://pypi.org/project/python-networkdays/ The package has no dependencies, no NumPy or pandas to calculate a date. ;)

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)]
Carlos Leite
  • 172
  • 5
0

For Python 3; xrange() is only for Python 2. Based on the answer from Dave Webb and includes code to show the days including weekends

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
cannin
  • 2,735
  • 2
  • 25
  • 32
0

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()
    ),
)
ijoseph
  • 6,505
  • 4
  • 26
  • 26
0

if you want a date, x business days away from a known date, you can do

import datetime
from pandas.tseries.offsets import BusinessDay
known_date = datetime.date(2022, 6, 7)
x = 7 # 7 business days away

date = known_date - 7 * BusinessDay() # Timestamp('2022-05-27 00:00:00')

if you want the series of dates

import pandas as pd
date_series  = pd.bdate_range(date, known_date) # DatetimeIndex(['2022-05-27', '2022-05-30', '2022-05-31', '2022-06-01','2022-06-02', '2022-06-03','2022-06-06', '2022-06-07'],dtype='datetime64[ns]', freq='B')
Chidi
  • 901
  • 11
  • 15
0

I want to suggest this (there is a bug that I fix in the solution of David Webb) that gives 64 instead of 63 days

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 [fromdate] + list(daygenerator) if day.weekday() < 5)


NB: we must add the first date (fromdate) at the top of the days generated to be sure that It'll be considered when excluding weekends

TawalMc
  • 1
  • 1
-1

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)`
varciasz
  • 26
  • 2