From 828108461959fb1a0127f7f32b8ab507d4053d7c Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sat, 15 Oct 2022 11:38:54 -0400 Subject: [PATCH 01/30] GH49075 looks weird. Let's add a test and go from there. --- pandas/tests/tseries/holiday/test_federal.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 64c60d4e365e6..9e1188ed3ca87 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -36,3 +36,20 @@ class MemorialDay(AbstractHolidayCalendar): datetime(1978, 5, 29, 0, 0), datetime(1979, 5, 28, 0, 0), ] + + +def test_holiday_calendar_inconsistent_returntype(): + # GH 49075 + + class AugustCalendar(AbstractHolidayCalendar): + rules = [] + + results_2018 = AugustCalendar().holidays( + start=datetime.date(2018, 8, 1), end=datetime.date(2018, 8, 31) + ) + results_2019 = AugustCalendar().holidays( + start=datetime.date(2019, 8, 1), end=datetime.date(2019, 8, 31) + ) + + assert type(results_2018) == DatetimeIndex + assert type(results_2019) == DatetimeIndex From 8d1b868244d3afb6239fc58ff22d877a59bc44f4 Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sat, 15 Oct 2022 14:19:02 -0400 Subject: [PATCH 02/30] Some cleanup. --- pandas/tests/tseries/holiday/test_federal.py | 19 +++++++------- pandas/tseries/holiday.py | 26 +++++++++++++------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 9e1188ed3ca87..27ed7b1fd0ae8 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -2,6 +2,7 @@ from pandas.tseries.holiday import ( AbstractHolidayCalendar, + USFederalHolidayCalendar, USMartinLutherKingJr, USMemorialDay, ) @@ -38,18 +39,18 @@ class MemorialDay(AbstractHolidayCalendar): ] -def test_holiday_calendar_inconsistent_returntype(): +def test_federal_holiday_inconsistent_returntype(): # GH 49075 - class AugustCalendar(AbstractHolidayCalendar): - rules = [] + # class test_calendar(USFederalHolidayCalendar): + # rules = [] + test_calendar= USFederalHolidayCalendar() - results_2018 = AugustCalendar().holidays( - start=datetime.date(2018, 8, 1), end=datetime.date(2018, 8, 31) + results_2018 = test_calendar().holidays( + start=datetime(2018, 8, 1), end=datetime(2018, 8, 31) ) - results_2019 = AugustCalendar().holidays( - start=datetime.date(2019, 8, 1), end=datetime.date(2019, 8, 31) + results_2019 = test_calendar().holidays( + start=datetime(2019, 8, 1), end=datetime(2019, 8, 31) ) - assert type(results_2018) == DatetimeIndex - assert type(results_2019) == DatetimeIndex + assert type(results_2018) == type(results_2019) diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index a8e55c4c2522f..e533b9ad7f15b 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -532,6 +532,20 @@ def merge(self, other, inplace: bool = False): USColumbusDay = Holiday( "Columbus Day", month=10, day=1, offset=DateOffset(weekday=MO(2)) ) +USJuneteenthIndependenceDay = Holiday( + "Juneteenth National Independence Day", + start_date=datetime(2021, 6, 18), + month=6, + day=19, + observance=nearest_workday, +) +USIndependenceDay = Holiday( + "Independence Day", + month=7, + day=4, + observance=nearest_workday, +) +USVeteransDay = Holiday("Veterans Day", month=11, day=11, observance=nearest_workday) USThanksgivingDay = Holiday( "Thanksgiving Day", month=11, day=1, offset=DateOffset(weekday=TH(4)) ) @@ -562,17 +576,11 @@ class USFederalHolidayCalendar(AbstractHolidayCalendar): USMartinLutherKingJr, USPresidentsDay, USMemorialDay, - Holiday( - "Juneteenth National Independence Day", - month=6, - day=19, - start_date="2021-06-18", - observance=nearest_workday, - ), - Holiday("Independence Day", month=7, day=4, observance=nearest_workday), + USJuneteenthIndependenceDay, + USIndependenceDay, USLaborDay, USColumbusDay, - Holiday("Veterans Day", month=11, day=11, observance=nearest_workday), + USVeteransDay, USThanksgivingDay, Holiday("Christmas Day", month=12, day=25, observance=nearest_workday), ] From eb5c8a8f3f7133355039af4965050ece22f01c46 Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sat, 15 Oct 2022 15:21:09 -0400 Subject: [PATCH 03/30] First pass at the fix. --- pandas/tests/tseries/holiday/test_federal.py | 6 +++--- pandas/tseries/holiday.py | 15 ++++----------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 27ed7b1fd0ae8..89f0f6782eed1 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -41,9 +41,9 @@ class MemorialDay(AbstractHolidayCalendar): def test_federal_holiday_inconsistent_returntype(): # GH 49075 - - # class test_calendar(USFederalHolidayCalendar): - # rules = [] + # User that the USFederalHolidayCalendar() would return inconsistent datatype + # for August before 2019. Noticed user was passing start/end as datetime.date(), + # but the error doesn't occur if start/end are passed as "YYYY-MM-DD". test_calendar= USFederalHolidayCalendar() results_2018 = test_calendar().holidays( diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index e533b9ad7f15b..1b86e8d73d0a1 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -532,20 +532,13 @@ def merge(self, other, inplace: bool = False): USColumbusDay = Holiday( "Columbus Day", month=10, day=1, offset=DateOffset(weekday=MO(2)) ) -USJuneteenthIndependenceDay = Holiday( +USJuneteenthIndepedence = Holiday( "Juneteenth National Independence Day", start_date=datetime(2021, 6, 18), month=6, day=19, observance=nearest_workday, ) -USIndependenceDay = Holiday( - "Independence Day", - month=7, - day=4, - observance=nearest_workday, -) -USVeteransDay = Holiday("Veterans Day", month=11, day=11, observance=nearest_workday) USThanksgivingDay = Holiday( "Thanksgiving Day", month=11, day=1, offset=DateOffset(weekday=TH(4)) ) @@ -576,11 +569,11 @@ class USFederalHolidayCalendar(AbstractHolidayCalendar): USMartinLutherKingJr, USPresidentsDay, USMemorialDay, - USJuneteenthIndependenceDay, - USIndependenceDay, + USJuneteenthIndepedence, + Holiday("Independence Day", month=7, day=4, observance=nearest_workday), USLaborDay, USColumbusDay, - USVeteransDay, + Holiday("Veterans Day", month=11, day=11, observance=nearest_workday), USThanksgivingDay, Holiday("Christmas Day", month=12, day=25, observance=nearest_workday), ] From b00c83fb4aa7577a6dfc4c1544badaaaa5b0fe68 Mon Sep 17 00:00:00 2001 From: Bill <35750915+roadswitcher@users.noreply.github.com> Date: Sat, 15 Oct 2022 15:51:35 -0400 Subject: [PATCH 04/30] Missed a space, failed formatting check. --- pandas/tests/tseries/holiday/test_federal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 89f0f6782eed1..71b319dcaa081 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -44,7 +44,7 @@ def test_federal_holiday_inconsistent_returntype(): # User that the USFederalHolidayCalendar() would return inconsistent datatype # for August before 2019. Noticed user was passing start/end as datetime.date(), # but the error doesn't occur if start/end are passed as "YYYY-MM-DD". - test_calendar= USFederalHolidayCalendar() + test_calendar = USFederalHolidayCalendar() results_2018 = test_calendar().holidays( start=datetime(2018, 8, 1), end=datetime(2018, 8, 31) From 17206003bb49c8d367598690e2090c7794d59a15 Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sat, 15 Oct 2022 16:34:36 -0400 Subject: [PATCH 05/30] pre-commit would have caught that, eh. --- pandas/tests/tseries/holiday/test_federal.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 89f0f6782eed1..86d09bd7f398c 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -42,9 +42,8 @@ class MemorialDay(AbstractHolidayCalendar): def test_federal_holiday_inconsistent_returntype(): # GH 49075 # User that the USFederalHolidayCalendar() would return inconsistent datatype - # for August before 2019. Noticed user was passing start/end as datetime.date(), - # but the error doesn't occur if start/end are passed as "YYYY-MM-DD". - test_calendar= USFederalHolidayCalendar() + # for August before 2019. + test_calendar = USFederalHolidayCalendar() results_2018 = test_calendar().holidays( start=datetime(2018, 8, 1), end=datetime(2018, 8, 31) From ad0a0174b24666b19b65033b5488d34fae0987d6 Mon Sep 17 00:00:00 2001 From: Bill <35750915+roadswitcher@users.noreply.github.com> Date: Sat, 15 Oct 2022 17:16:28 -0400 Subject: [PATCH 06/30] Update test_federal.py --- pandas/tests/tseries/holiday/test_federal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 86d09bd7f398c..11acf779aebdd 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -45,10 +45,10 @@ def test_federal_holiday_inconsistent_returntype(): # for August before 2019. test_calendar = USFederalHolidayCalendar() - results_2018 = test_calendar().holidays( + results_2018 = test_calendar.holidays( start=datetime(2018, 8, 1), end=datetime(2018, 8, 31) ) - results_2019 = test_calendar().holidays( + results_2019 = test_calendar.holidays( start=datetime(2019, 8, 1), end=datetime(2019, 8, 31) ) From c07f068f76bfc817defd44093ccf88b44f6edbcc Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sun, 16 Oct 2022 17:42:51 -0400 Subject: [PATCH 07/30] Well, that was subtle. --- pandas/tseries/holiday.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index 1b86e8d73d0a1..a8e55c4c2522f 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -532,13 +532,6 @@ def merge(self, other, inplace: bool = False): USColumbusDay = Holiday( "Columbus Day", month=10, day=1, offset=DateOffset(weekday=MO(2)) ) -USJuneteenthIndepedence = Holiday( - "Juneteenth National Independence Day", - start_date=datetime(2021, 6, 18), - month=6, - day=19, - observance=nearest_workday, -) USThanksgivingDay = Holiday( "Thanksgiving Day", month=11, day=1, offset=DateOffset(weekday=TH(4)) ) @@ -569,7 +562,13 @@ class USFederalHolidayCalendar(AbstractHolidayCalendar): USMartinLutherKingJr, USPresidentsDay, USMemorialDay, - USJuneteenthIndepedence, + Holiday( + "Juneteenth National Independence Day", + month=6, + day=19, + start_date="2021-06-18", + observance=nearest_workday, + ), Holiday("Independence Day", month=7, day=4, observance=nearest_workday), USLaborDay, USColumbusDay, From b423bb74d5afbacf0d3f7dcaedaa95bc4a21461a Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sun, 16 Oct 2022 17:56:30 -0400 Subject: [PATCH 08/30] Changed the test. --- pandas/tests/tseries/holiday/test_federal.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 11acf779aebdd..9a91dc03a10b1 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -41,15 +41,16 @@ class MemorialDay(AbstractHolidayCalendar): def test_federal_holiday_inconsistent_returntype(): # GH 49075 - # User that the USFederalHolidayCalendar() would return inconsistent datatype - # for August before 2019. - test_calendar = USFederalHolidayCalendar() - results_2018 = test_calendar.holidays( + # Instantiate two calendars to rule out _cache + cal1 = USFederalHolidayCalendar() + cal2 = USFederalHolidayCalendar() + + results_2018 = cal1.holidays( start=datetime(2018, 8, 1), end=datetime(2018, 8, 31) ) - results_2019 = test_calendar.holidays( + results_2019 = cal2.holidays( start=datetime(2019, 8, 1), end=datetime(2019, 8, 31) ) - assert type(results_2018) == type(results_2019) + assert type(results_2018) == type(results_2019) \ No newline at end of file From 9f893261eb216546bb8967f49c2eeddac6b7fba0 Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sun, 16 Oct 2022 18:00:01 -0400 Subject: [PATCH 09/30] Git mistake. --- pandas/tseries/holiday.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index a8e55c4c2522f..1c199857f1ca7 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -220,9 +220,9 @@ class from pandas.tseries.offsets self.day = day self.offset = offset self.start_date = ( - Timestamp(start_date) if start_date is not None else start_date + Timestamp(start_date) if start_date is not None else AbstractHolidayCalendar.start_date ) - self.end_date = Timestamp(end_date) if end_date is not None else end_date + self.end_date = Timestamp(end_date) if end_date is not None else AbstractHolidayCalendar.end_date self.observance = observance assert days_of_week is None or type(days_of_week) == tuple self.days_of_week = days_of_week From 992143c1bf9121d71f4721c0c540d07566d1922f Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sun, 16 Oct 2022 19:25:30 -0400 Subject: [PATCH 10/30] Backing away from keyboard now. --- pandas/tests/tseries/holiday/test_federal.py | 12 ++++-------- pandas/tseries/holiday.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 9a91dc03a10b1..ec90ba14828f8 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -46,11 +46,7 @@ def test_federal_holiday_inconsistent_returntype(): cal1 = USFederalHolidayCalendar() cal2 = USFederalHolidayCalendar() - results_2018 = cal1.holidays( - start=datetime(2018, 8, 1), end=datetime(2018, 8, 31) - ) - results_2019 = cal2.holidays( - start=datetime(2019, 8, 1), end=datetime(2019, 8, 31) - ) - - assert type(results_2018) == type(results_2019) \ No newline at end of file + results_2018 = cal1.holidays(start=datetime(2018, 8, 1), end=datetime(2018, 8, 31)) + results_2019 = cal2.holidays(start=datetime(2019, 8, 1), end=datetime(2019, 8, 31)) + + assert type(results_2018) == type(results_2019) diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index 1c199857f1ca7..da106dcf02710 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -220,9 +220,15 @@ class from pandas.tseries.offsets self.day = day self.offset = offset self.start_date = ( - Timestamp(start_date) if start_date is not None else AbstractHolidayCalendar.start_date + Timestamp(start_date) + if start_date is not None + else AbstractHolidayCalendar.start_date + ) + self.end_date = ( + Timestamp(end_date) + if end_date is not None + else AbstractHolidayCalendar.end_date ) - self.end_date = Timestamp(end_date) if end_date is not None else AbstractHolidayCalendar.end_date self.observance = observance assert days_of_week is None or type(days_of_week) == tuple self.days_of_week = days_of_week From 7486888c83eefd7467fe1892553559d639cadde5 Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sat, 29 Oct 2022 16:32:21 -0400 Subject: [PATCH 11/30] next: write more tests. --- pandas/tests/tseries/holiday/test_federal.py | 3 +++ pandas/tseries/holiday.py | 12 +++--------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index ec90ba14828f8..9d11407633211 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -7,6 +7,8 @@ USMemorialDay, ) +import pytest + def test_no_mlk_before_1986(): # see gh-10278 @@ -39,6 +41,7 @@ class MemorialDay(AbstractHolidayCalendar): ] +# @pytest.mark.xfail(reason="working on GH49075") def test_federal_holiday_inconsistent_returntype(): # GH 49075 diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index 638290f7a6597..f3b2ec0a9c604 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -220,15 +220,9 @@ class from pandas.tseries.offsets self.day = day self.offset = offset self.start_date = ( - Timestamp(start_date) - if start_date is not None - else AbstractHolidayCalendar.start_date - ) - self.end_date = ( - Timestamp(end_date) - if end_date is not None - else AbstractHolidayCalendar.end_date + Timestamp(start_date) if start_date is not None else start_date ) + self.end_date = Timestamp(end_date) if end_date is not None else end_date self.observance = observance assert days_of_week is None or type(days_of_week) == tuple self.days_of_week = days_of_week @@ -342,7 +336,7 @@ def _apply_rule(self, dates): Dates with rules applied """ if self.observance is not None: - return dates.map(lambda d: self.observance(d)) + return DatetimeIndex([dates.map(lambda d: self.observance(d))]) if self.offset is not None: if not isinstance(self.offset, list): From d63ea7080e1ed7c78e4f36e06ae3bb8c3ba95ce0 Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sat, 29 Oct 2022 17:06:40 -0400 Subject: [PATCH 12/30] precommit local run. --- pandas/tests/tseries/holiday/test_federal.py | 2 +- pandas/tseries/holiday.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 9d11407633211..6262e10b323cf 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -41,7 +41,7 @@ class MemorialDay(AbstractHolidayCalendar): ] -# @pytest.mark.xfail(reason="working on GH49075") +@pytest.mark.xfail(reason="working on GH49075") def test_federal_holiday_inconsistent_returntype(): # GH 49075 diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index f3b2ec0a9c604..cb65fc958414f 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -336,7 +336,7 @@ def _apply_rule(self, dates): Dates with rules applied """ if self.observance is not None: - return DatetimeIndex([dates.map(lambda d: self.observance(d))]) + return dates.map(lambda d: self.observance(d)) if self.offset is not None: if not isinstance(self.offset, list): From e40ce4df89cd145f1535cfde8255a772f73567ce Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sat, 29 Oct 2022 17:34:03 -0400 Subject: [PATCH 13/30] User specified half-open date intervals can return inconsistent results. --- pandas/tests/tseries/holiday/test_federal.py | 2 -- pandas/tseries/holiday.py | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 6262e10b323cf..0d6c5d77c2f33 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -7,7 +7,6 @@ USMemorialDay, ) -import pytest def test_no_mlk_before_1986(): @@ -41,7 +40,6 @@ class MemorialDay(AbstractHolidayCalendar): ] -@pytest.mark.xfail(reason="working on GH49075") def test_federal_holiday_inconsistent_returntype(): # GH 49075 diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index cb65fc958414f..0e61ad6293981 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -227,6 +227,12 @@ class from pandas.tseries.offsets assert days_of_week is None or type(days_of_week) == tuple self.days_of_week = days_of_week + # GH 49075 + if start_date is not None and observance is not None and end_date is None: + self.end_date = AbstractHolidayCalendar.end_date + if end_date is not None and observance is not None and start_date is None: + self.start_date = AbstractHolidayCalendar.start_date + def __repr__(self) -> str: info = "" if self.year is not None: From c953651caec776cac4a9b541831c959c6bcfd444 Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sat, 29 Oct 2022 21:38:07 -0400 Subject: [PATCH 14/30] Added logic to close open time intervals into AbstractHolidayCalender --- pandas/tseries/holiday.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index 0e61ad6293981..e91c479ec38df 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -227,12 +227,6 @@ class from pandas.tseries.offsets assert days_of_week is None or type(days_of_week) == tuple self.days_of_week = days_of_week - # GH 49075 - if start_date is not None and observance is not None and end_date is None: - self.end_date = AbstractHolidayCalendar.end_date - if end_date is not None and observance is not None and start_date is None: - self.start_date = AbstractHolidayCalendar.start_date - def __repr__(self) -> str: info = "" if self.year is not None: @@ -426,6 +420,29 @@ def rule_from_name(self, name): return None + def _close_rule_intervals(self) -> None: + # GH 49075/49118 + open_intervals_end = [ + rule + for rule in self.rules + if rule.start_date is not None + and rule.observance is not None + and rule.end_date is None + ] + if open_intervals_end: + for holiday in open_intervals_end: + holiday.end_date = AbstractHolidayCalendar.end_date + open_intervals_start = [ + rule + for rule in self.rules + if rule.end_date is not None + and rule.observance is not None + and rule.start_date is None + ] + if open_intervals_start: + for holiday in open_intervals_start: + holiday.start_date = AbstractHolidayCalendar.start_date + def holidays(self, start=None, end=None, return_name: bool = False): """ Returns a curve with holidays between start_date and end_date @@ -463,6 +480,8 @@ def holidays(self, start=None, end=None, return_name: bool = False): rule.dates(start, end, return_name=True) for rule in self.rules ] if pre_holidays: + # GH 49075 + self._close_rule_intervals() holidays = concat(pre_holidays) else: holidays = Series(index=DatetimeIndex([]), dtype=object) From 68f8db4e5677a237def8ccb970717e9957fc8fec Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sat, 29 Oct 2022 22:16:04 -0400 Subject: [PATCH 15/30] OK, I've learned pre-commit doesn't pass wildcards all the way down a directory tree. --- pandas/tests/tseries/holiday/test_federal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 0d6c5d77c2f33..ec90ba14828f8 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -8,7 +8,6 @@ ) - def test_no_mlk_before_1986(): # see gh-10278 class MLKCalendar(AbstractHolidayCalendar): From 3172392f024ddd201f19e8829a20b623247b1f75 Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sun, 30 Oct 2022 11:10:06 -0400 Subject: [PATCH 16/30] Ensure DatetimeIndex returned in _apply_rule --- pandas/tests/tseries/holiday/test_federal.py | 4 +- pandas/tests/tseries/holiday/test_holiday.py | 42 ++++++++++++++++++++ pandas/tseries/holiday.py | 25 ------------ 3 files changed, 45 insertions(+), 26 deletions(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index ec90ba14828f8..23687894229d1 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -1,5 +1,7 @@ from datetime import datetime +import pytest + from pandas.tseries.holiday import ( AbstractHolidayCalendar, USFederalHolidayCalendar, @@ -39,9 +41,9 @@ class MemorialDay(AbstractHolidayCalendar): ] +@pytest.mark.xfail(reason="See GH 49075") def test_federal_holiday_inconsistent_returntype(): # GH 49075 - # Instantiate two calendars to rule out _cache cal1 = USFederalHolidayCalendar() cal2 = USFederalHolidayCalendar() diff --git a/pandas/tests/tseries/holiday/test_holiday.py b/pandas/tests/tseries/holiday/test_holiday.py index cefb2f86703b2..193fc01775806 100644 --- a/pandas/tests/tseries/holiday/test_holiday.py +++ b/pandas/tests/tseries/holiday/test_holiday.py @@ -264,3 +264,45 @@ def test_both_offset_observance_raises(): offset=[DateOffset(weekday=SA(4))], observance=next_monday, ) + + +@pytest.mark.xfail(reason="Working on GH49075") +def test_half_open_interval_with_observance(): + # See GH 49075. + holiday_1 = Holiday( + "Arbitrary Holiday - start 2022-03-14", + start_date=datetime.date(2022, 3, 14), + month=3, + day=14, + observance=next_monday, + ) + holiday_2 = Holiday( + "Arbitrary Holiday 2 - end 2022-03-20", + end_date=datetime.date(2022, 3, 20), + month=3, + day=20, + observance=next_monday, + ) + + class TestHolidayCalendar(AbstractHolidayCalendar): + rules = [ + USMartinLutherKingJr, + holiday_1, + holiday_2, + USLaborDay, + ] + + start_datum = Timestamp("2022-08-01") + end_datum = Timestamp("2022-08-31") + test_cal = TestHolidayCalendar() + + three_years_before = test_cal.holidays( + start_datum - DateOffset(years=3), end_datum - DateOffset(years=3) + ) + year_of = test_cal.holidays(start_datum, end_datum) + three_years_after = test_cal.holidays( + start_datum + DateOffset(years=3), end_datum + DateOffset(years=3) + ) + + assert type(three_years_after) == type(year_of) + assert type(three_years_before) == type(year_of) diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index e91c479ec38df..cb65fc958414f 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -420,29 +420,6 @@ def rule_from_name(self, name): return None - def _close_rule_intervals(self) -> None: - # GH 49075/49118 - open_intervals_end = [ - rule - for rule in self.rules - if rule.start_date is not None - and rule.observance is not None - and rule.end_date is None - ] - if open_intervals_end: - for holiday in open_intervals_end: - holiday.end_date = AbstractHolidayCalendar.end_date - open_intervals_start = [ - rule - for rule in self.rules - if rule.end_date is not None - and rule.observance is not None - and rule.start_date is None - ] - if open_intervals_start: - for holiday in open_intervals_start: - holiday.start_date = AbstractHolidayCalendar.start_date - def holidays(self, start=None, end=None, return_name: bool = False): """ Returns a curve with holidays between start_date and end_date @@ -480,8 +457,6 @@ def holidays(self, start=None, end=None, return_name: bool = False): rule.dates(start, end, return_name=True) for rule in self.rules ] if pre_holidays: - # GH 49075 - self._close_rule_intervals() holidays = concat(pre_holidays) else: holidays = Series(index=DatetimeIndex([]), dtype=object) From cb6ab53f168d1a32c405fc9ec197b8aaf017ed25 Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sun, 30 Oct 2022 11:10:52 -0400 Subject: [PATCH 17/30] Ensure DatetimeIndex returned in _apply_rule --- pandas/tests/tseries/holiday/test_federal.py | 3 +-- pandas/tests/tseries/holiday/test_holiday.py | 5 ++--- pandas/tseries/holiday.py | 10 ++++++++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 23687894229d1..20530c16ae106 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -41,9 +41,8 @@ class MemorialDay(AbstractHolidayCalendar): ] -@pytest.mark.xfail(reason="See GH 49075") def test_federal_holiday_inconsistent_returntype(): - # GH 49075 + # GH 49075 test case # Instantiate two calendars to rule out _cache cal1 = USFederalHolidayCalendar() cal2 = USFederalHolidayCalendar() diff --git a/pandas/tests/tseries/holiday/test_holiday.py b/pandas/tests/tseries/holiday/test_holiday.py index 193fc01775806..6d2ea8913dc44 100644 --- a/pandas/tests/tseries/holiday/test_holiday.py +++ b/pandas/tests/tseries/holiday/test_holiday.py @@ -266,19 +266,18 @@ def test_both_offset_observance_raises(): ) -@pytest.mark.xfail(reason="Working on GH49075") def test_half_open_interval_with_observance(): # See GH 49075. holiday_1 = Holiday( "Arbitrary Holiday - start 2022-03-14", - start_date=datetime.date(2022, 3, 14), + start_date=datetime(2022, 3, 14), month=3, day=14, observance=next_monday, ) holiday_2 = Holiday( "Arbitrary Holiday 2 - end 2022-03-20", - end_date=datetime.date(2022, 3, 20), + end_date=datetime(2022, 3, 20), month=3, day=20, observance=next_monday, diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index cb65fc958414f..7222a6d9585a8 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -335,6 +335,16 @@ def _apply_rule(self, dates): ------- Dates with rules applied """ + # GH 49075 + # If we have a half-open date interval with observance, + # ensure we return a DatetimeIndex + if ( + self.observance is not None + and (self.start_date is not None and self.end_date is None) + or (self.end_date is not None and self.start_date is None) + ): + return DatetimeIndex([dates.map(lambda d: self.observance(d))]) + if self.observance is not None: return dates.map(lambda d: self.observance(d)) From b865481a1ae0fb946c19e0825105a056aecc371e Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sun, 30 Oct 2022 11:33:25 -0400 Subject: [PATCH 18/30] Caught formatting. --- pandas/tests/tseries/holiday/test_federal.py | 1 - pandas/tseries/holiday.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 20530c16ae106..bebe0d28c74c6 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -1,6 +1,5 @@ from datetime import datetime -import pytest from pandas.tseries.holiday import ( AbstractHolidayCalendar, diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index 7222a6d9585a8..78198aa945c48 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -336,7 +336,7 @@ def _apply_rule(self, dates): Dates with rules applied """ # GH 49075 - # If we have a half-open date interval with observance, + # If we have a half-open date interval with observance, # ensure we return a DatetimeIndex if ( self.observance is not None From 0c8e325cb268593ce885cb686cb9473fea9dea95 Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Sun, 30 Oct 2022 12:09:58 -0400 Subject: [PATCH 19/30] Missed running isort locally. --- pandas/tests/tseries/holiday/test_federal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index bebe0d28c74c6..2f083bc9a771e 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -1,6 +1,5 @@ from datetime import datetime - from pandas.tseries.holiday import ( AbstractHolidayCalendar, USFederalHolidayCalendar, From 5aca82dcbcf994f814ea7d584900b47d07fa77e2 Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Mon, 31 Oct 2022 20:17:07 -0400 Subject: [PATCH 20/30] Well, that was subtle. --- pandas/tests/tseries/holiday/test_federal.py | 4 +++- pandas/tests/tseries/holiday/test_holiday.py | 24 ++++++++++++-------- pandas/tseries/holiday.py | 13 +++-------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 2f083bc9a771e..de260e912cab5 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -1,5 +1,7 @@ from datetime import datetime +import pandas._testing as tm + from pandas.tseries.holiday import ( AbstractHolidayCalendar, USFederalHolidayCalendar, @@ -48,4 +50,4 @@ def test_federal_holiday_inconsistent_returntype(): results_2018 = cal1.holidays(start=datetime(2018, 8, 1), end=datetime(2018, 8, 31)) results_2019 = cal2.holidays(start=datetime(2019, 8, 1), end=datetime(2019, 8, 31)) - assert type(results_2018) == type(results_2019) + tm.assert_index_equal(results_2018, results_2019) diff --git a/pandas/tests/tseries/holiday/test_holiday.py b/pandas/tests/tseries/holiday/test_holiday.py index 6d2ea8913dc44..94c38c58f996b 100644 --- a/pandas/tests/tseries/holiday/test_holiday.py +++ b/pandas/tests/tseries/holiday/test_holiday.py @@ -267,7 +267,11 @@ def test_both_offset_observance_raises(): def test_half_open_interval_with_observance(): - # See GH 49075. + # Prompted by GH 49075 + # Given holidays with a half-open date interval and a defined + # observance pattern, we check to make sure that the return type + # for Holiday.dates() remains consistent before/after the date that + # marks the 'edge' of the half-open date interval holiday_1 = Holiday( "Arbitrary Holiday - start 2022-03-14", start_date=datetime(2022, 3, 14), @@ -291,17 +295,17 @@ class TestHolidayCalendar(AbstractHolidayCalendar): USLaborDay, ] - start_datum = Timestamp("2022-08-01") - end_datum = Timestamp("2022-08-31") + start = Timestamp("2022-08-01") + end = Timestamp("2022-08-31") test_cal = TestHolidayCalendar() - three_years_before = test_cal.holidays( - start_datum - DateOffset(years=3), end_datum - DateOffset(years=3) + check_before_edge = test_cal.holidays( + start - DateOffset(years=3), end - DateOffset(years=3) ) - year_of = test_cal.holidays(start_datum, end_datum) - three_years_after = test_cal.holidays( - start_datum + DateOffset(years=3), end_datum + DateOffset(years=3) + check_year_of = test_cal.holidays(start, end) + check_after_edge = test_cal.holidays( + start + DateOffset(years=3), end + DateOffset(years=3) ) - assert type(three_years_after) == type(year_of) - assert type(three_years_before) == type(year_of) + tm.assert_index_equal(check_after_edge, check_year_of) + tm.assert_index_equal(check_before_edge, check_year_of) diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index 78198aa945c48..2b6d036889324 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -322,7 +322,7 @@ def _reference_dates(self, start_date, end_date): return dates - def _apply_rule(self, dates): + def _apply_rule(self, dates: DatetimeIndex) -> DatetimeIndex: """ Apply the given offset/observance to a DatetimeIndex of dates. @@ -335,15 +335,8 @@ def _apply_rule(self, dates): ------- Dates with rules applied """ - # GH 49075 - # If we have a half-open date interval with observance, - # ensure we return a DatetimeIndex - if ( - self.observance is not None - and (self.start_date is not None and self.end_date is None) - or (self.end_date is not None and self.start_date is None) - ): - return DatetimeIndex([dates.map(lambda d: self.observance(d))]) + if dates.empty: + return DatetimeIndex([]) if self.observance is not None: return dates.map(lambda d: self.observance(d)) From c6b2d5bdfb24801a3eb1fcb3ae79b56bc7c76659 Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Mon, 31 Oct 2022 20:59:23 -0400 Subject: [PATCH 21/30] Changed URL to current OPM source-of-record, updated whatsnew --- doc/source/whatsnew/v2.0.0.rst | 3 ++- pandas/tseries/holiday.py | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index 5614b7a2c0846..c4e09fb5ae694 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -327,7 +327,8 @@ Datetimelike - Bug in :func:`to_datetime` was raising on invalid offsets with ``errors='coerce'`` and ``infer_datetime_format=True`` (:issue:`48633`) - Bug in :class:`DatetimeIndex` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``dtype`` or data (:issue:`48659`) - Bug in subtracting a ``datetime`` scalar from :class:`DatetimeIndex` failing to retain the original ``freq`` attribute (:issue:`48818`) -- +- Bug in ``pandas.tseries.holiday.Holiday`` causing inconsistent return types for custom :class:`AbstractHolidayCalendar` instances (:issue:`49075`) +- Timedelta ^^^^^^^^^ diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index 2b6d036889324..77a9fc6ea0915 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -556,8 +556,7 @@ def merge(self, other, inplace: bool = False): class USFederalHolidayCalendar(AbstractHolidayCalendar): """ US Federal Government Holiday Calendar based on rules specified by: - https://www.opm.gov/policy-data-oversight/ - snow-dismissal-procedures/federal-holidays/ + https://www.opm.gov/policy-data-oversight/pay-leave/federal-holidays/ """ rules = [ From a8a6244879c77018d08c8065374c23e8dd870559 Mon Sep 17 00:00:00 2001 From: Bill <35750915+roadswitcher@users.noreply.github.com> Date: Tue, 1 Nov 2022 01:49:13 -0400 Subject: [PATCH 22/30] Update v2.0.0.rst --- doc/source/whatsnew/v2.0.0.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index 5be232804f252..8c34a681e4b77 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -340,7 +340,6 @@ Datetimelike - Bug in :class:`DatetimeIndex` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``dtype`` or data (:issue:`48659`) - Bug in subtracting a ``datetime`` scalar from :class:`DatetimeIndex` failing to retain the original ``freq`` attribute (:issue:`48818`) - Bug in ``pandas.tseries.holiday.Holiday`` causing inconsistent return types for custom :class:`AbstractHolidayCalendar` instances (:issue:`49075`) -- Timedelta ^^^^^^^^^ From ec087a338336c39be8d869a366e08ce1568a8504 Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Tue, 1 Nov 2022 02:44:33 -0400 Subject: [PATCH 23/30] Add dtype to empty index ( caught by mypy in CI, not part of pre-commit ) --- pandas/tseries/holiday.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index 77a9fc6ea0915..e4b60174d7628 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -335,8 +335,9 @@ def _apply_rule(self, dates: DatetimeIndex) -> DatetimeIndex: ------- Dates with rules applied """ + # Found due to GH49075 if dates.empty: - return DatetimeIndex([]) + return DatetimeIndex([], dtype="datetime64[ns]") if self.observance is not None: return dates.map(lambda d: self.observance(d)) From a6242a9fce13793e92e83a8baeb5dbb5251a7ca9 Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Tue, 1 Nov 2022 06:15:29 -0400 Subject: [PATCH 24/30] Updated holiday.py --- pandas/tseries/holiday.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index e4b60174d7628..76df57e1ba8a3 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -322,7 +322,7 @@ def _reference_dates(self, start_date, end_date): return dates - def _apply_rule(self, dates: DatetimeIndex) -> DatetimeIndex: + def _apply_rule(self, dates): """ Apply the given offset/observance to a DatetimeIndex of dates. @@ -335,9 +335,9 @@ def _apply_rule(self, dates: DatetimeIndex) -> DatetimeIndex: ------- Dates with rules applied """ - # Found due to GH49075 if dates.empty: return DatetimeIndex([], dtype="datetime64[ns]") + if self.observance is not None: return dates.map(lambda d: self.observance(d)) From 892212305a0c49aa5877ee18b24f48b4e0c8cc37 Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Tue, 1 Nov 2022 06:19:00 -0400 Subject: [PATCH 25/30] ... and forgot to pre-commit run black --- pandas/tseries/holiday.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index 76df57e1ba8a3..da3d4dd88c644 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -337,7 +337,6 @@ def _apply_rule(self, dates): """ if dates.empty: return DatetimeIndex([], dtype="datetime64[ns]") - if self.observance is not None: return dates.map(lambda d: self.observance(d)) From a502a06925149f3cebc90d02df9ead72aab2035a Mon Sep 17 00:00:00 2001 From: Bill <35750915+roadswitcher@users.noreply.github.com> Date: Tue, 1 Nov 2022 13:24:51 -0400 Subject: [PATCH 26/30] Update pandas/tseries/holiday.py Co-authored-by: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> --- pandas/tseries/holiday.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index da3d4dd88c644..0583b714ea101 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -336,7 +336,7 @@ def _apply_rule(self, dates): Dates with rules applied """ if dates.empty: - return DatetimeIndex([], dtype="datetime64[ns]") + return DatetimeIndex([]) if self.observance is not None: return dates.map(lambda d: self.observance(d)) From 67c83b8a74d4221a3805695cfb6ab3b62b47b350 Mon Sep 17 00:00:00 2001 From: Bill <35750915+roadswitcher@users.noreply.github.com> Date: Tue, 1 Nov 2022 13:25:49 -0400 Subject: [PATCH 27/30] Update v2.0.0.rst --- doc/source/whatsnew/v2.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index 5ad5682a172c3..c0f73841c6aa7 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -340,7 +340,7 @@ Datetimelike - Bug in :func:`to_datetime` was raising on invalid offsets with ``errors='coerce'`` and ``infer_datetime_format=True`` (:issue:`48633`) - Bug in :class:`DatetimeIndex` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``dtype`` or data (:issue:`48659`) - Bug in subtracting a ``datetime`` scalar from :class:`DatetimeIndex` failing to retain the original ``freq`` attribute (:issue:`48818`) -- Bug in ``pandas.tseries.holiday.Holiday`` causing inconsistent return types for custom :class:`AbstractHolidayCalendar` instances (:issue:`49075`) +- Bug in ``pandas.tseries.holiday.Holiday`` where a half-open date interval causes inconsistent return types from :meth:`AbstractHolidayCalendar.holidays` (:issue:`49075`) Timedelta ^^^^^^^^^ From 6188c80f88f10834695b244b98b927050de6d5fb Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Tue, 1 Nov 2022 19:44:39 -0400 Subject: [PATCH 28/30] Added test to test_federal.py to ensure comparison against known-good result in addition to type comparison --- pandas/tests/tseries/holiday/test_federal.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index de260e912cab5..9d20b9b429dfe 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -49,5 +49,11 @@ def test_federal_holiday_inconsistent_returntype(): results_2018 = cal1.holidays(start=datetime(2018, 8, 1), end=datetime(2018, 8, 31)) results_2019 = cal2.holidays(start=datetime(2019, 8, 1), end=datetime(2019, 8, 31)) - + expected_res = DatetimeIndex([], dtype='datetime64[ns]', freq=None) + # Check to make sure the returned types are identical + # per GH49075 example code tm.assert_index_equal(results_2018, results_2019) + # Check each against expected results to rule out if + # the half-open interval logic has failed for one case + tm.assert_index_equal(results_2018, expected_res) + tm.assert_index_equal(results_2019, expected_res) \ No newline at end of file From 42831159012c0ed23786eb9018143562874102dc Mon Sep 17 00:00:00 2001 From: Bill Blum Date: Tue, 1 Nov 2022 20:43:53 -0400 Subject: [PATCH 29/30] Add change to test_federal to compare against constructed DatetimeIndex, cleaned up test_half_open_interval_with_observance() in test_holiday. --- pandas/tests/tseries/holiday/test_federal.py | 15 +++++------ pandas/tests/tseries/holiday/test_holiday.py | 28 +++++++++++--------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 9d20b9b429dfe..2565877f8a2a4 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -1,5 +1,6 @@ from datetime import datetime +from pandas import DatetimeIndex import pandas._testing as tm from pandas.tseries.holiday import ( @@ -49,11 +50,9 @@ def test_federal_holiday_inconsistent_returntype(): results_2018 = cal1.holidays(start=datetime(2018, 8, 1), end=datetime(2018, 8, 31)) results_2019 = cal2.holidays(start=datetime(2019, 8, 1), end=datetime(2019, 8, 31)) - expected_res = DatetimeIndex([], dtype='datetime64[ns]', freq=None) - # Check to make sure the returned types are identical - # per GH49075 example code - tm.assert_index_equal(results_2018, results_2019) - # Check each against expected results to rule out if - # the half-open interval logic has failed for one case - tm.assert_index_equal(results_2018, expected_res) - tm.assert_index_equal(results_2019, expected_res) \ No newline at end of file + expected_results = DatetimeIndex([], dtype="datetime64[ns]", freq=None) + + # Check against expected results to ensure both date + # ranges generate expected results as per GH49075 submission + tm.assert_index_equal(results_2018, expected_results) + tm.assert_index_equal(results_2019, expected_results) diff --git a/pandas/tests/tseries/holiday/test_holiday.py b/pandas/tests/tseries/holiday/test_holiday.py index 94c38c58f996b..ee83ca144d38a 100644 --- a/pandas/tests/tseries/holiday/test_holiday.py +++ b/pandas/tests/tseries/holiday/test_holiday.py @@ -3,6 +3,7 @@ import pytest from pytz import utc +from pandas import DatetimeIndex import pandas._testing as tm from pandas.tseries.holiday import ( @@ -268,10 +269,12 @@ def test_both_offset_observance_raises(): def test_half_open_interval_with_observance(): # Prompted by GH 49075 - # Given holidays with a half-open date interval and a defined - # observance pattern, we check to make sure that the return type - # for Holiday.dates() remains consistent before/after the date that - # marks the 'edge' of the half-open date interval + # Check for holidays that have a half-open date interval where + # they have either a start_date or end_date defined along + # with a defined observance pattern to make sure that the return type + # for Holiday.dates() remains consistent before & after the year that + # marks the 'edge' of the half-open date interval. + holiday_1 = Holiday( "Arbitrary Holiday - start 2022-03-14", start_date=datetime(2022, 3, 14), @@ -297,15 +300,14 @@ class TestHolidayCalendar(AbstractHolidayCalendar): start = Timestamp("2022-08-01") end = Timestamp("2022-08-31") + year_offset = DateOffset(years=5) + expected_results = DatetimeIndex([], dtype="datetime64[ns]", freq=None) test_cal = TestHolidayCalendar() - check_before_edge = test_cal.holidays( - start - DateOffset(years=3), end - DateOffset(years=3) - ) - check_year_of = test_cal.holidays(start, end) - check_after_edge = test_cal.holidays( - start + DateOffset(years=3), end + DateOffset(years=3) - ) + date_interval_low = test_cal.holidays(start - year_offset, end - year_offset) + date_window_edge = test_cal.holidays(start, end) + date_interval_high = test_cal.holidays(start + year_offset, end + year_offset) - tm.assert_index_equal(check_after_edge, check_year_of) - tm.assert_index_equal(check_before_edge, check_year_of) + tm.assert_index_equal(date_interval_low, expected_results) + tm.assert_index_equal(date_window_edge, expected_results) + tm.assert_index_equal(date_interval_high, expected_results) From 45521a7623ed4193003bd8b825085c4541de5440 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Wed, 2 Nov 2022 09:29:22 -0700 Subject: [PATCH 30/30] Update doc/source/whatsnew/v2.0.0.rst --- doc/source/whatsnew/v2.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index ed546705a5df3..dfea3d450fa8a 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -353,7 +353,7 @@ Datetimelike - Bug in :func:`to_datetime` was raising on invalid offsets with ``errors='coerce'`` and ``infer_datetime_format=True`` (:issue:`48633`) - Bug in :class:`DatetimeIndex` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``dtype`` or data (:issue:`48659`) - Bug in subtracting a ``datetime`` scalar from :class:`DatetimeIndex` failing to retain the original ``freq`` attribute (:issue:`48818`) -- Bug in ``pandas.tseries.holiday.Holiday`` where a half-open date interval causes inconsistent return types from :meth:`AbstractHolidayCalendar.holidays` (:issue:`49075`) +- Bug in ``pandas.tseries.holiday.Holiday`` where a half-open date interval causes inconsistent return types from :meth:`USFederalHolidayCalendar.holidays` (:issue:`49075`) Timedelta ^^^^^^^^^