diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 3cd7d696b..f43a76909 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -178,6 +178,42 @@ To get a list of other categories holidays (for countries that support them): 2023-12-25 Christmas Day 2023-12-26 Bank Holiday +Working day-related calculations +-------------------------------- + +To check if the specified date is a working day: + +.. code-block:: python + + >>> us_holidays = holidays.US(years=2024) # Weekends in the US are Saturday and Sunday. + >>> us_holidays.is_working_day("2024-01-01") # Monday, New Year's Day. + False + >>> us_holidays.is_working_day("2024-01-02") # Tuesday, ordinary day. + True + >>> us_holidays.is_working_day("2024-01-06") # Saturday, ordinary day. + False + >>> us_holidays.is_working_day("2024-01-15") # Monday, Martin Luther King Jr. Day. + False + +To find the nth working day after the specified date: + +.. code-block:: python + + >>> us_holidays.get_nth_working_day("2024-12-20", 5) + datetime.date(2024, 12, 30) + +Here we calculate the 5th working day after December 20, 2024. Working days are 23 (Mon), +24 (Tue), 26 (Thu), 27 (Fri), 30 (Mon); 21-22, 28-29 - weekends, 25 - Christmas Day. + +To calculate the number or working days between two specified dates: + +.. code-block:: python + + >>> us_holidays.get_working_days_count("2024-04-01", "2024-06-30") + 63 + +Here we calculate the number of working days in Q2 2024. + Date from holiday name ---------------------- diff --git a/holidays/holiday_base.py b/holidays/holiday_base.py index ea8f40159..3eb8e1c27 100644 --- a/holidays/holiday_base.py +++ b/holidays/holiday_base.py @@ -961,7 +961,7 @@ def get_named( raise AttributeError(f"Unknown lookup type: {lookup}") - def get_nth_workday(self, key: DateLike, n: int) -> date: + def get_nth_working_day(self, key: DateLike, n: int) -> date: """Return n-th working day from provided date (if n is positive) or n-th working day before provided date (if n is negative). """ @@ -969,22 +969,30 @@ def get_nth_workday(self, key: DateLike, n: int) -> date: dt = self.__keytransform__(key) for _ in range(abs(n)): dt = _timedelta(dt, direction) - while not self.is_workday(dt): + while not self.is_working_day(dt): dt = _timedelta(dt, direction) return dt - def get_workdays_number(self, key1: DateLike, key2: DateLike) -> int: - """Return the number of working days between two dates (not including the start date).""" - dt1 = self.__keytransform__(key1) - dt2 = self.__keytransform__(key2) - if dt1 == dt2: - return 0 + def get_working_days_count(self, start: DateLike, end: DateLike) -> int: + """Return the number of working days between two dates. + + The date range works in a closed interval fashion [start, end] so both + endpoints are included. + + :param start: + The range start date. + + :param end: + The range end date. + """ + dt1 = self.__keytransform__(start) + dt2 = self.__keytransform__(end) if dt1 > dt2: dt1, dt2 = dt2, dt1 + days = (dt2 - dt1).days + 1 + return sum(self.is_working_day(_timedelta(dt1, n)) for n in range(days)) - return sum(self.is_workday(_timedelta(dt1, n)) for n in range(1, (dt2 - dt1).days + 1)) - - def is_workday(self, key: DateLike) -> bool: + def is_working_day(self, key: DateLike) -> bool: """Return True if date is a working day (not a holiday or a weekend).""" dt = self.__keytransform__(key) return dt in self.weekend_workdays if self._is_weekend(dt) else dt not in self diff --git a/tests/test_holiday_base.py b/tests/test_holiday_base.py index 833dc63ca..8aedf42ad 100644 --- a/tests/test_holiday_base.py +++ b/tests/test_holiday_base.py @@ -1140,51 +1140,51 @@ class TestWorkdays(unittest.TestCase): def setUp(self): self.hb = CountryStub6(years=2024) - def test_is_workday(self): - self.assertTrue(self.hb.is_workday("2024-02-12")) - self.assertFalse(self.hb.is_workday("2024-02-17")) - self.assertFalse(self.hb.is_workday("2024-02-19")) - self.assertTrue(self.hb.is_workday("2024-02-24")) - - self.assertTrue(self.hb.is_workday("2024-04-30")) - self.assertFalse(self.hb.is_workday("2024-05-01")) - self.assertFalse(self.hb.is_workday("2024-05-02")) - self.assertTrue(self.hb.is_workday("2024-05-03")) - - def test_get_nth_workday(self): - self.assertEqual(self.hb.get_nth_workday("2024-01-04", 0), date(2024, 1, 4)) - self.assertEqual(self.hb.get_nth_workday("2024-01-04", +1), date(2024, 1, 5)) - self.assertEqual(self.hb.get_nth_workday("2024-01-04", +3), date(2024, 1, 9)) - self.assertEqual(self.hb.get_nth_workday("2024-01-06", +1), date(2024, 1, 8)) - self.assertEqual(self.hb.get_nth_workday("2024-01-26", -10), date(2024, 1, 12)) - self.assertEqual(self.hb.get_nth_workday("2024-01-21", -1), date(2024, 1, 19)) - - self.assertEqual(self.hb.get_nth_workday("2024-02-15", +4), date(2024, 2, 22)) - self.assertEqual(self.hb.get_nth_workday("2024-02-15", +5), date(2024, 2, 23)) - self.assertEqual(self.hb.get_nth_workday("2024-02-15", +6), date(2024, 2, 24)) - self.assertEqual(self.hb.get_nth_workday("2024-02-15", +7), date(2024, 2, 26)) - self.assertEqual(self.hb.get_nth_workday("2024-02-26", -7), date(2024, 2, 15)) - self.assertEqual(self.hb.get_nth_workday("2024-02-25", -7), date(2024, 2, 15)) - - self.assertEqual(self.hb.get_nth_workday("2024-04-29", +1), date(2024, 4, 30)) - self.assertEqual(self.hb.get_nth_workday("2024-04-29", +2), date(2024, 5, 3)) - self.assertEqual(self.hb.get_nth_workday("2024-04-29", +3), date(2024, 5, 6)) - self.assertEqual(self.hb.get_nth_workday("2024-04-29", +4), date(2024, 5, 7)) - self.assertEqual(self.hb.get_nth_workday("2024-05-10", -10), date(2024, 4, 24)) - self.assertEqual(self.hb.get_nth_workday("2024-05-10", -7), date(2024, 4, 29)) - self.assertEqual(self.hb.get_nth_workday("2024-05-10", -5), date(2024, 5, 3)) - - def test_get_workdays_number(self): - self.assertEqual(self.hb.get_workdays_number("2024-01-03", "2024-01-23"), 14) - self.assertEqual(self.hb.get_workdays_number("2024-01-23", "2024-01-03"), 14) - self.assertEqual(self.hb.get_workdays_number("2024-01-06", "2024-01-07"), 0) - self.assertEqual(self.hb.get_workdays_number("2024-01-16", "2024-01-16"), 0) - - self.assertEqual(self.hb.get_workdays_number("2024-02-08", "2024-02-15"), 5) - self.assertEqual(self.hb.get_workdays_number("2024-02-15", "2024-02-22"), 4) - self.assertEqual(self.hb.get_workdays_number("2024-02-22", "2024-02-29"), 6) - - self.assertEqual(self.hb.get_workdays_number("2024-04-29", "2024-05-03"), 2) - self.assertEqual(self.hb.get_workdays_number("2024-04-29", "2024-05-04"), 2) - self.assertEqual(self.hb.get_workdays_number("2024-04-29", "2024-05-05"), 2) - self.assertEqual(self.hb.get_workdays_number("2024-04-29", "2024-05-06"), 3) + def test_is_working_day(self): + self.assertTrue(self.hb.is_working_day("2024-02-12")) + self.assertFalse(self.hb.is_working_day("2024-02-17")) + self.assertFalse(self.hb.is_working_day("2024-02-19")) + self.assertTrue(self.hb.is_working_day("2024-02-24")) + + self.assertTrue(self.hb.is_working_day("2024-04-30")) + self.assertFalse(self.hb.is_working_day("2024-05-01")) + self.assertFalse(self.hb.is_working_day("2024-05-02")) + self.assertTrue(self.hb.is_working_day("2024-05-03")) + + def test_get_nth_working_day(self): + self.assertEqual(self.hb.get_nth_working_day("2024-01-04", 0), date(2024, 1, 4)) + self.assertEqual(self.hb.get_nth_working_day("2024-01-04", +1), date(2024, 1, 5)) + self.assertEqual(self.hb.get_nth_working_day("2024-01-04", +3), date(2024, 1, 9)) + self.assertEqual(self.hb.get_nth_working_day("2024-01-06", +1), date(2024, 1, 8)) + self.assertEqual(self.hb.get_nth_working_day("2024-01-26", -10), date(2024, 1, 12)) + self.assertEqual(self.hb.get_nth_working_day("2024-01-21", -1), date(2024, 1, 19)) + + self.assertEqual(self.hb.get_nth_working_day("2024-02-15", +4), date(2024, 2, 22)) + self.assertEqual(self.hb.get_nth_working_day("2024-02-15", +5), date(2024, 2, 23)) + self.assertEqual(self.hb.get_nth_working_day("2024-02-15", +6), date(2024, 2, 24)) + self.assertEqual(self.hb.get_nth_working_day("2024-02-15", +7), date(2024, 2, 26)) + self.assertEqual(self.hb.get_nth_working_day("2024-02-26", -7), date(2024, 2, 15)) + self.assertEqual(self.hb.get_nth_working_day("2024-02-25", -7), date(2024, 2, 15)) + + self.assertEqual(self.hb.get_nth_working_day("2024-04-29", +1), date(2024, 4, 30)) + self.assertEqual(self.hb.get_nth_working_day("2024-04-29", +2), date(2024, 5, 3)) + self.assertEqual(self.hb.get_nth_working_day("2024-04-29", +3), date(2024, 5, 6)) + self.assertEqual(self.hb.get_nth_working_day("2024-04-29", +4), date(2024, 5, 7)) + self.assertEqual(self.hb.get_nth_working_day("2024-05-10", -10), date(2024, 4, 24)) + self.assertEqual(self.hb.get_nth_working_day("2024-05-10", -7), date(2024, 4, 29)) + self.assertEqual(self.hb.get_nth_working_day("2024-05-10", -5), date(2024, 5, 3)) + + def test_get_working_days_count(self): + self.assertEqual(self.hb.get_working_days_count("2024-01-03", "2024-01-23"), 15) + self.assertEqual(self.hb.get_working_days_count("2024-01-23", "2024-01-03"), 15) + self.assertEqual(self.hb.get_working_days_count("2024-01-06", "2024-01-07"), 0) + self.assertEqual(self.hb.get_working_days_count("2024-01-16", "2024-01-16"), 1) + + self.assertEqual(self.hb.get_working_days_count("2024-02-08", "2024-02-15"), 6) + self.assertEqual(self.hb.get_working_days_count("2024-02-15", "2024-02-22"), 5) + self.assertEqual(self.hb.get_working_days_count("2024-02-22", "2024-02-29"), 7) + + self.assertEqual(self.hb.get_working_days_count("2024-04-29", "2024-05-03"), 3) + self.assertEqual(self.hb.get_working_days_count("2024-04-29", "2024-05-04"), 3) + self.assertEqual(self.hb.get_working_days_count("2024-04-29", "2024-05-05"), 3) + self.assertEqual(self.hb.get_working_days_count("2024-04-29", "2024-05-06"), 4)