BUG: Return type discrepancy in USFederalHolidayCalendar
State: closed

- [x] closes #49075
- [x] [Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [x] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).

- [x] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.


Labels:
name: Frequency
description: DateOffsets

Comments:
Disregard, found weird state, updating original issue with findings.
OK, I was overthinking the debugging process...

- Replicate submitters problem
- Check a time window with an actual federal holiday
- Try submitter's problem again and.... it didn't repeat?    Guess there's a cache somewhere, time to go find it.

Created a local `ArbitraryHolidayCalendar`, started copying the US holidays into the `rules[]` section -- noticed that soon as I added a definition for Juneteenth the output I got for a similar command resulted in `Index` and not `DatetimeIndex` being returned.   Noted that if I deleted the `start-date` definition in Juneteenth , the problem went away.

Led me here, which in turn led me to look at the use of the class variables for start date vs per-Holiday use.

https://github.com/pandas-dev/pandas/blob/8608ac919fbad7fb656d6b9bc5ddf7f09ae7a430/pandas/tseries/holiday.py#L277-L284


take
This comment from #11484 jumps out at me: 
https://github.com/pandas-dev/pandas/pull/11484/files#r43535505

I think in this context-  if a `Holiday()` is defined with a specific `start_date`, without an `end_date` provided, it would be reasonable to assume that the calendar's end date ( `AbstractHolidayCalendar.end_date` ) would be the effective `end_date` for internal consistency within the specific calendar instance.    

I need to write a few more tests for the other behavior I observed while poking around at this.



OK, added logic into `AbstractHolidayCalendar()` to check for cases where a custom defined holiday has a half-open date interval along with an `observance` method., and set the other date to match the end of the Abstract calendar.

This doesn't affect custom defined holidays that use the `offset` logic ( like the US MLK Jr holiday ).



Did some more testing - it's not the `concat()` in holiday.py -- given the following date range that has no holidays from the calendar, the behavior is observed +/- three years from the date marking the 'closed' side of the date interval, but not within that time frame.    


```
# C:\Users\billb\Desktop>python gh49075_test.py

# PANDAS VERSION: 1.5.1
# GH 49075: Expected type of cal is DatetimeIndex:False

# Before the window: <class 'pandas.core.indexes.base.Index'>
# Year of the holiday: <class 'pandas.core.indexes.datetimes.DatetimeIndex'>
# After the window <class 'pandas.core.indexes.base.Index'>


import datetime
from pandas.tseries.holiday import (
    USFederalHolidayCalendar, Holiday, nearest_workday,
    AbstractHolidayCalendar, sunday_to_monday, USMartinLutherKingJr, USLaborDay,
)
from pandas import (
    Timestamp, date_range, __version__, DatetimeIndex, DateOffset
)

print(f"\nPANDAS VERSION: {__version__}")

### Replicate GH49075 submission
cal = USFederalHolidayCalendar()
print(f"GH 49075: Expected type of cal is DatetimeIndex:{type(cal)==DatetimeIndex}\n")

### Some testing
holiday_1 = Holiday(
    "Arbitrary Holiday - start 2022-03-14",
    start_date = datetime.date(2022,3,14),
    month=3,
    day=14,
    observance=nearest_workday, 
) 
holiday_2 = Holiday(
    "Arbitrary Holiday 2 - end 2022-03-20",
    end_date = datetime.date(2022,3,20),
    month=3,
    day=20,
    observance=sunday_to_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))
                                      
print(f"Before the window: {type(three_years_before)}")
print(f"Year of the holiday: {type(year_of)}")
print(f"After the window {type(three_years_after)}")
```
I think this is probably more suitable than my initial attempt


> Also needs a whatnew note in `v2.0.0.rst` (`Datetimelike` bug fix section)

Will do-- didn't know which particular version number to update at this time.
Awesome work @roadswitcher (CI failures looked unreleated)