Most recent previous business day in Python

PythonDatetime

Python Problem Overview


I need to subtract business days from the current date.

I currently have some code which needs always to be running on the most recent business day. So that may be today if we're Monday thru Friday, but if it's Saturday or Sunday then I need to set it back to the Friday before the weekend. I currently have some pretty clunky code to do this:

 lastBusDay = datetime.datetime.today()
 if datetime.date.weekday(lastBusDay) == 5:      #if it's Saturday
     lastBusDay = lastBusDay - datetime.timedelta(days = 1) #then make it Friday
 elif datetime.date.weekday(lastBusDay) == 6:      #if it's Sunday
     lastBusDay = lastBusDay - datetime.timedelta(days = 2); #then make it Friday

Is there a better way?

Can I tell timedelta to work in weekdays rather than calendar days for example?

Python Solutions


Solution 1 - Python

Use pandas!

import datetime
# BDay is business day, not birthday...
from pandas.tseries.offsets import BDay

today = datetime.datetime.today()
print(today - BDay(4))

Since today is Thursday, Sept 26, that will give you an output of:

datetime.datetime(2013, 9, 20, 14, 8, 4, 89761)

Solution 2 - Python

If you want to skip US holidays as well as weekends, this worked for me (using pandas 0.23.3):

import pandas as pd
from pandas.tseries.holiday import USFederalHolidayCalendar
from pandas.tseries.offsets import CustomBusinessDay
US_BUSINESS_DAY = CustomBusinessDay(calendar=USFederalHolidayCalendar())
july_5 = pd.datetime(2018, 7, 5)
result = july_5 - 2 * US_BUSINESS_DAY # 2018-7-2

To convert to a python date object I did this:

result.to_pydatetime().date()

Solution 3 - Python

Maybe this code could help:

lastBusDay = datetime.datetime.today()
shift = datetime.timedelta(max(1,(lastBusDay.weekday() + 6) % 7 - 3))
lastBusDay = lastBusDay - shift

The idea is that on Mondays yo have to go back 3 days, on Sundays 2, and 1 in any other day.

The statement (lastBusDay.weekday() + 6) % 7 just re-bases the Monday from 0 to 6.

Really don't know if this will be better in terms of performance.

Solution 4 - Python

There seem to be several options if you're open to installing extra libraries.

This post describes a way of defining workdays with dateutil.

http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-09/3758.html

BusinessHours lets you custom-define your list of holidays, etc., to define when your working hours (and by extension working days) are.

http://pypi.python.org/pypi/BusinessHours/

Solution 5 - Python

DISCLAMER: I'm the author...

I wrote a package that does exactly this, business dates calculations. You can use custom week specification and holidays.

I had this exact problem while working with financial data and didn't find any of the available solutions particularly easy, so I wrote one.

Hope this is useful for other people.

https://pypi.python.org/pypi/business_calendar/

Solution 6 - Python

If somebody is looking for solution respecting holidays (without any huge library like pandas), try this function:

import holidays
import datetime


def previous_working_day(check_day_, holidays=holidays.US()):
    offset = max(1, (check_day_.weekday() + 6) % 7 - 3)
    most_recent = check_day_ - datetime.timedelta(offset)
    if most_recent not in holidays:
        return most_recent
    else:
        return previous_working_day(most_recent, holidays)

check_day = datetime.date(2020, 12, 28)
previous_working_day(check_day)

which produces:

datetime.date(2020, 12, 24)

Solution 7 - Python

timeboard package does this.

Suppose your date is 04 Sep 2017. In spite of being a Monday, it was a holiday in the US (the Labor Day). So, the most recent business day was Friday, Sep 1.

>>> import timeboard.calendars.US as US
>>> clnd = US.Weekly8x5()
>>> clnd('04 Sep 2017').rollback().to_timestamp().date()
datetime.date(2017, 9, 1)

In UK, 04 Sep 2017 was the regular business day, so the most recent business day was itself.

>>> import timeboard.calendars.UK as UK
>>> clnd = UK.Weekly8x5()
>>> clnd('04 Sep 2017').rollback().to_timestamp().date()
datetime.date(2017, 9, 4)

DISCLAIMER: I am the author of timeboard.

Solution 8 - Python

This will give a generator of working days, of course without holidays, stop is datetime.datetime object. If you need holidays just make additional argument with list of holidays and check with 'IFology' ;-)

def workingdays(stop, start=datetime.date.today()):
    while start != stop:
        if start.weekday() < 5:
            yield start
        start += datetime.timedelta(1)

Later on you can count them like

workdays = workingdays(datetime.datetime(2015, 8, 8))
len(list(workdays))

Solution 9 - Python

For the pandas usecase, I found the following to be quite useful and compact, although not completely readable:

Get most recent previous business day:

In [2]: datetime.datetime(2019, 11, 30) + BDay(1) - BDay(1)  # Saturday
Out[2]: Timestamp('2019-11-29 00:00:00')


In [3]: datetime.datetime(2019, 11, 29) + BDay(1) - BDay(1)  # Friday
Out[3]: Timestamp('2019-11-29 00:00:00')

In the other direction, simply use:

In [4]: datetime.datetime(2019, 11, 30) + BDay(0)  # Saturday
Out[4]: Timestamp('2019-12-02 00:00:00')

In [5]: datetime.datetime(2019, 11, 29) + BDay(0)  # Friday
Out[5]: Timestamp('2019-11-29 00:00:00')

Solution 10 - Python

 def getNthBusinessDay(startDate, businessDaysInBetween):
	currentDate = startDate
	daysToAdd = businessDaysInBetween
	while daysToAdd > 0:
		currentDate += relativedelta(days=1)
		day = currentDate.weekday()
		if day < 5:
			daysToAdd -= 1

	return currentDate 

Solution 11 - Python

Why don't you try something like:

lastBusDay = datetime.datetime.today()
if datetime.date.weekday(lastBusDay) not in range(0,5):
    lastBusDay = 5

Solution 12 - Python

another simplify version

lastBusDay = datetime.datetime.today()
wk_day = datetime.date.weekday(lastBusDay)
if wk_day > 4:      #if it's Saturday or Sunday
    lastBusDay = lastBusDay - datetime.timedelta(days = wk_day-4) #then make it Friday

Solution 13 - Python

Solution irrespective of different jurisdictions having different holidays:

If you need to find the right id within a table, you can use this snippet. The Table model is a sqlalchemy model and the dates to search from are in the field day.

def last_relevant_date(db: Session, given_date: date) -> int:
    available_days = (db.query(Table.id, Table.day)
                      .order_by(desc(Table.day))
                      .limit(100).all())
    close_dates = pd.DataFrame(available_days)
    close_dates['delta'] = close_dates['day'] - given_date
    past_dates = (close_dates
                  .loc[close_dates['delta'] < pd.Timedelta(0, unit='d')])
    table_id = int(past_dates.loc[past_dates['delta'].idxmax()]['id'])
    return table_id

This is not a solution that I would recommend when you have to convert in bulk. It is rather generic and expensive as you are not using joins. Moreover, it assumes that you have a relevant day that is one of the 100 most recent days in the model Table. So it tackles data input that may have different dates.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionThomas BrowneView Question on Stackoverflow
Solution 1 - PythonKyle HannonView Answer on Stackoverflow
Solution 2 - PythonlakenenView Answer on Stackoverflow
Solution 3 - PythonTMCView Answer on Stackoverflow
Solution 4 - PythonAlison R.View Answer on Stackoverflow
Solution 5 - PythonantoniobotelhoView Answer on Stackoverflow
Solution 6 - PythonZdPo SterView Answer on Stackoverflow
Solution 7 - PythonMaxim MamaevView Answer on Stackoverflow
Solution 8 - PythonmastierView Answer on Stackoverflow
Solution 9 - PythonycoView Answer on Stackoverflow
Solution 10 - PythonTheManView Answer on Stackoverflow
Solution 11 - PythonValentinSView Answer on Stackoverflow
Solution 12 - PythonCircleOnCirclesView Answer on Stackoverflow
Solution 13 - PythonPatrickView Answer on Stackoverflow