Skip to content

Instantly share code, notes, and snippets.

@itsff
Created February 3, 2019 05:08
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save itsff/9575b987102459b82523d4c79fb22c3f to your computer and use it in GitHub Desktop.
Save itsff/9575b987102459b82523d4c79fb22c3f to your computer and use it in GitHub Desktop.
Finding next business day in Python
from datetime import datetime, timedelta
def find_next_biz_day(days_away=1,
start=None,
is_holiday=lambda d: False):
"""
Finds a business day that is N days away
:param days_away: Days away (positive or negative)
:param start: Starting date (today if None)
:param is_holiday: custom function accepting date and returning True if it's a holiday
:return: date
"""
if start is None:
start = datetime.today()
result_date = start
if days_away == 0:
return result_date
elif days_away > 0:
delta = timedelta(days=1)
else:
delta = timedelta(days=-1)
n = abs(days_away)
def _is_weekend(test_date):
day = test_date.isoweekday()
return day == 6 or day == 7
while True:
# Keep going to the next day when weekend or holiday
if _is_weekend(result_date) or is_holiday(result_date):
result_date += delta
continue
if n <= 0:
break
# Found a workday
result_date += delta
n -= 1
return result_date
import unittest
from datetime import date
from biz_day import find_next_biz_day
class BizDayTests(unittest.TestCase):
saturday = date(2019, 2, 2)
sunday = date(2019, 2, 3)
monday = date(2019, 2, 4)
tuesday = date(2019, 2, 5)
wednesday = date(2019, 2, 6)
thursday = date(2019, 2, 7)
friday = date(2019, 2, 8)
monday2 = date(2019, 2, 11)
tuesday2 = date(2019, 2, 12)
wednesday2 = date(2019, 2, 13)
thursday2 = date(2019, 2, 14)
friday2 = date(2019, 2, 15)
def test_zero_weekend_or_not(self):
self.assertEqual(self.saturday,
find_next_biz_day(days_away=0, start=self.saturday),
'Value of zero should result in same day')
def test_standard_work_week_1(self):
self.assertEqual(self.tuesday,
find_next_biz_day(1, start=self.monday))
self.assertEqual(self.wednesday,
find_next_biz_day(1, start=self.tuesday))
self.assertEqual(self.thursday,
find_next_biz_day(1, start=self.wednesday))
self.assertEqual(self.friday,
find_next_biz_day(1, start=self.thursday))
def test_standard_work_week_negative_1(self):
self.assertEqual(self.monday,
find_next_biz_day(-1, start=self.tuesday))
self.assertEqual(self.tuesday,
find_next_biz_day(-1, start=self.wednesday))
self.assertEqual(self.wednesday,
find_next_biz_day(-1, start=self.thursday))
self.assertEqual(self.thursday,
find_next_biz_day(-1, start=self.friday))
def test_standard_work_week_2(self):
self.assertEqual(self.wednesday,
find_next_biz_day(2, start=self.monday),
'M+2b -> W')
self.assertEqual(self.thursday,
find_next_biz_day(2, start=self.tuesday),
'T+2b -> R')
self.assertEqual(self.friday,
find_next_biz_day(2, start=self.wednesday),
'W+2b -> F')
self.assertEqual(self.monday2,
find_next_biz_day(2, start=self.thursday),
'R+2b -> M2')
self.assertEqual(self.tuesday2,
find_next_biz_day(2, start=self.friday),
'F+2b -> T2')
def test_standard_work_week_negative_2(self):
self.assertEqual(self.monday,
find_next_biz_day(-2, start=self.wednesday),
'W-2b -> M')
self.assertEqual(self.tuesday,
find_next_biz_day(-2, start=self.thursday),
'R-2b -> T')
self.assertEqual(self.wednesday,
find_next_biz_day(-2, start=self.friday),
'F-2b -> W')
self.assertEqual(self.thursday,
find_next_biz_day(-2, start=self.monday2),
'M2-2b -> R')
self.assertEqual(self.friday,
find_next_biz_day(-2, start=self.tuesday2),
'T2-2b -> F')
def test_over_the_weekend(self):
self.assertEqual(self.monday2,
find_next_biz_day(5, start=self.monday),
'M+5b -> M2')
self.assertEqual(self.monday2,
find_next_biz_day(1, start=self.friday),
'F+1b -> M2')
self.assertEqual(self.tuesday2,
find_next_biz_day(2, start=self.friday),
'M+2b -> T2')
def test_over_the_weekend_negative(self):
self.assertEqual(self.monday,
find_next_biz_day(-5, start=self.monday2),
'M2-5b -> M')
self.assertEqual(self.friday,
find_next_biz_day(-1, start=self.monday2),
'M2-1b -> F')
self.assertEqual(self.friday,
find_next_biz_day(-2, start=self.tuesday2),
'T2-2b -> F')
@staticmethod
def all_fridays_are_holidays(date):
day = date.isoweekday()
is_holiday = day == 5
return is_holiday
def test_custom_holiday_function(self):
self.assertEqual(self.monday2,
find_next_biz_day(days_away=1,
start=self.thursday,
is_holiday=self.all_fridays_are_holidays),
'Thursday +1b (skip F) -> M')
self.assertEqual(self.wednesday,
find_next_biz_day(days_away=-3,
start=self.tuesday2,
is_holiday=self.all_fridays_are_holidays),
'Tuesday -3b (skip F) -> W')
def test_custom_holiday_lambda(self):
self.assertEqual(self.thursday,
find_next_biz_day(days_away=1,
start=self.tuesday,
is_holiday=lambda d: d.isoweekday() == 3),
'Tuesday +1b (skip W) -> Thursday')
def test_custom_common_holidays(self):
# Inner helper function
def is_common_holiday(test_date):
holidays = [
(12, 24), # christmas eve
(12, 25), # christmas day
(1, 1), # new year
(7, 4), # 4th of July
]
for m, d in holidays:
if test_date.day == d and test_date.month == m:
return True
return False
# New Years
self.assertEqual(date(2019, 1, 2),
find_next_biz_day(days_away=1,
start=date(2018, 12, 31),
is_holiday=is_common_holiday),
'Mon Dec31 +1b (skip New Years) -> Wed Jan 2nd')
# Christmas Eve and Day
self.assertEqual(date(2018, 12, 26),
find_next_biz_day(days_away=2,
start=date(2018, 12, 20),
is_holiday=is_common_holiday),
'Thr Dec20 +2b (skip Christmas eye and day) -> Wed Dec26')
# 4th of July
self.assertEqual(date(2019, 7, 3),
find_next_biz_day(days_away=-1,
start=date(2019, 7, 5),
is_holiday=is_common_holiday),
'Fri July5 -1b (skip 4th of July) -> Wed July3')
if __name__ == '__main__':
unittest.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment