Python 中最近的前一个工作日

我需要从当前日期中减去 工作日

我目前有一些代码,需要始终运行在最近的工作日。如果是星期一到星期五,也许是今天,但是如果是星期六或星期天,那么我需要把它设置为周末前的星期五。我目前有一些漂亮的 笨重代码这样做:

 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

还有更好的办法吗?

例如,我可以告诉 timedelta 在工作日而不是日历日工作吗?

178044 次浏览

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/

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.

Why don't you try something like:

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

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

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/

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

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.

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

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

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)

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

When I am writing this answer, today is Friday in USA so next business day shall be Monday, in the meantime yesterday is thanksgiving holiday so previous business day should be Wednesday

So today date of Friday, November 24, 2022, is a perfect time to get the previous, current and next business days.

By having trial and error, I could only find the correct output by combining the method as below:

from datetime import datetime, timedelta


from pandas.tseries.offsets import BDay
from pandas.tseries.offsets import CustomBusinessDay
from pandas.tseries.holiday import USFederalHolidayCalendar




US_BUSINESS_DAY = CustomBusinessDay(calendar=USFederalHolidayCalendar())


TODAY = datetime.today() - 1 * US_BUSINESS_DAY
YESTERDAY = (datetime.today() - timedelta(max(1,(TODAY.weekday() + 6) % 7 - 3))) - 1 * US_BUSINESS_DAY
TOMORROW = TODAY + BDay(1)


DAY_NAME = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday','Sunday']


BUSINESS_DATE = "[Previous (" + DAY_NAME[YESTERDAY.weekday()] + "):'" + YESTERDAY.strftime('%y%m%d')
BUSINESS_DATE += "', Current (" + DAY_NAME[TODAY.weekday()] + "):'" + TODAY.strftime('%y%m%d')
BUSINESS_DATE +=  "', Next (" + DAY_NAME[TOMORROW.weekday()] + "):'" + TOMORROW.strftime('%y%m%d') + "']"


print_("Business Date USA = ", BUSINESS_DATE)


Output:

Business Date USA = [Previous (Wednesday):'221123', Current (Friday):'221125', Next (Monday):'221128']