Repository: pandas-dev/pandas

Issue:
Error in business-day logic in CustomBusinessDay?
#### Code Sample, a copy-pastable example if possible

```python
# Your code here

# Labor Day calendar

import pandas as pd
from pandas.tseries.holiday import AbstractHolidayCalendar, USLaborDay

class testCalendar(AbstractHolidayCalendar):
    rules = [
        USLaborDay,
    ]

cal = testCalendar()
workDay = pd.offsets.CustomBusinessDay(calendar=cal)

# Print dates of Labor Day
for yr in [2021, 2032]:
    print("Labor day", yr, "is on ",  cal.holidays(pd.to_datetime(str(yr-1)+'-12-31'), 
                                                    pd.to_datetime(str(yr) + '-12-31')))

print("\n")
                                                                   
# Check next working day for Saturday before Labor Day
for dateText in ['2021-09-04', '2032-09-04']:
    date = pd.to_datetime(dateText)
    print(dateText, "is a", date.strftime('%A') + ". Roll forward to", date + 0 * workDay)


```
#### Problem description

Here's the output:

Labor day 2021 is on  DatetimeIndex(['2021-09-06'], dtype='datetime64[ns]', freq='WOM-1MON')
Labor day 2032 is on  DatetimeIndex(['2032-09-06'], dtype='datetime64[ns]', freq='WOM-1MON')


2021-09-04 is a Saturday. Roll forward to 2021-09-07 00:00:00
2032-09-04 is a Saturday. Roll forward to 2032-09-06 00:00:00

The calendar knows that Labor day is on Monday, Sept. 6 in both 2021 and 2032. However, when I ask to move to the next working day from Saturday, Sept. 4, I get the (correct) answer Sept.7 for 2021, but the (wrong) answer Sept 6 (which is Labor Day) for 2032.

Is this a bug, or am I doing something wrong?

Thanks.

**Note**: We receive a lot of issues on our GitHub tracker, so it is very possible that your issue has been posted before. Please check first before submitting so that we do not have to handle and close duplicates!

**Note**: Many problems can be resolved by simply upgrading `pandas` to the latest version. Before submitting, please check if that solution works for you. If possible, you may want to check if `master` addresses this issue, but that is not necessary.

For documentation-related issues, you can check the latest versions of the docs on `master` here:

https://pandas-docs.github.io/pandas-docs-travis/

If the issue has not been resolved there, go ahead and file it in the issue tracker.

#### Expected Output

#### Output of ``pd.show_versions()``




<details>

[paste the output of ``pd.show_versions()`` here below this line]

INSTALLED VERSIONS
------------------
commit: None
python: 3.7.3.final.0
python-bits: 64
OS: Darwin
OS-release: 17.7.0
machine: x86_64
processor: i386
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

pandas: 0.24.2
pytest: 5.0.1
pip: 19.1.1
setuptools: 41.0.1
Cython: 0.29.12
numpy: 1.16.4
scipy: 1.3.0
pyarrow: None
xarray: None
IPython: 7.6.1
sphinx: 2.1.2
patsy: 0.5.1
dateutil: 2.8.0
pytz: 2019.1
blosc: None
bottleneck: 1.2.1
tables: 3.5.2
numexpr: 2.6.9
feather: None
matplotlib: 3.1.0
openpyxl: 2.6.2
xlrd: 1.2.0
xlwt: 1.3.0
xlsxwriter: 1.1.8
lxml.etree: 4.3.4
bs4: 4.7.1
html5lib: 1.0.1
sqlalchemy: 1.3.5
pymysql: None
psycopg2: None
jinja2: 2.10.1
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None
gcsfs: None
</details>



Discussion:
tseries.holidays gets relatively little attention, so it is entirely plausible that this is a bug.

I've got a guess as to the cause, would like you to confirm.  Is it the case that the behavior is correct for 2030 but broken for 2031?  And correct for 1970 but broken for 1969?
You're correct. Here are my results so far:

1969: Broken
1970: OK
2021: OK
2030: OK
2031: Broken
2032: Broken


That's good news indeed.  Then I'm pretty sure the issue is caued by the start_date and end_date defined on L348-349 of tseries.holiday.  Extending those dates out further will probably fix this problem.  That might come with a performance penalty (or else I expect the original author would have had these go out further in the first place), so it isn't obvious how far out they should be extended.

A PR for this would be welcome.
Let me take a look. We need to be able to go out at least 50 years.

Resolving patch:
diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py
--- a/pandas/tseries/holiday.py
+++ b/pandas/tseries/holiday.py
@@ -346,7 +346,7 @@ class AbstractHolidayCalendar(metaclass=HolidayCalendarMetaClass):
 
     rules = []  # type: List[Holiday]
     start_date = Timestamp(datetime(1970, 1, 1))
-    end_date = Timestamp(datetime(2030, 12, 31))
+    end_date = Timestamp(datetime(2200, 12, 31))
     _cache = None
 
     def __init__(self, name=None, rules=None):


Test patch:
diff --git a/pandas/tests/groupby/test_categorical.py b/pandas/tests/groupby/test_categorical.py
--- a/pandas/tests/groupby/test_categorical.py
+++ b/pandas/tests/groupby/test_categorical.py
@@ -784,7 +784,8 @@ def test_categorical_no_compress():
 
 def test_sort():
 
-    # http://stackoverflow.com/questions/23814368/sorting-pandas-categorical-labels-after-groupby  # noqa: E501
+    # http://stackoverflow.com/questions/23814368/sorting-pandas-
+    #        categorical-labels-after-groupby
     # This should result in a properly sorted Series so that the plot
     # has a sorted x axis
     # self.cat.groupby(['value_group'])['value_group'].count().plot(kind='bar')
diff --git a/pandas/tests/tseries/holiday/test_calendar.py b/pandas/tests/tseries/holiday/test_calendar.py
--- a/pandas/tests/tseries/holiday/test_calendar.py
+++ b/pandas/tests/tseries/holiday/test_calendar.py
@@ -2,7 +2,7 @@
 
 import pytest
 
-from pandas import DatetimeIndex
+from pandas import DatetimeIndex, offsets, to_datetime
 import pandas.util.testing as tm
 
 from pandas.tseries.holiday import (
@@ -10,6 +10,7 @@
     Holiday,
     Timestamp,
     USFederalHolidayCalendar,
+    USLaborDay,
     USThanksgivingDay,
     get_calendar,
 )
@@ -81,3 +82,19 @@ def test_calendar_observance_dates():
 def test_rule_from_name():
     us_fed_cal = get_calendar("USFederalHolidayCalendar")
     assert us_fed_cal.rule_from_name("Thanksgiving") == USThanksgivingDay
+
+
+def test_calendar_2031():
+    # See gh-27790
+    #
+    # Labor Day 2031 is on September 1. Saturday before is August 30.
+    # Next working day after August 30 ought to be Tuesday, September 2.
+
+    class testCalendar(AbstractHolidayCalendar):
+        rules = [USLaborDay]
+
+    cal = testCalendar()
+    workDay = offsets.CustomBusinessDay(calendar=cal)
+    Sat_before_Labor_Day_2031 = to_datetime("2031-08-30")
+    next_working_day = Sat_before_Labor_Day_2031 + 0 * workDay
+    assert next_working_day == to_datetime("2031-09-02")