Created
February 3, 2019 05:08
-
-
Save itsff/9575b987102459b82523d4c79fb22c3f to your computer and use it in GitHub Desktop.
Finding next business day in Python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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