Repository: pandas-dev/pandas

Issue:
Disable class level caching in AbstractHolidayCalendar
By caching at the _class_ level it prevents instances from defining different rules - e.g.

``` python
import numpy as np
from pandas.tseries.holiday import AbstractHolidayCalendar, Holiday

class HolidayCalendar(AbstractHolidayCalendar):
    def __init__(self, rules):
        self.rules = list(np.atleast_1d(rules))

jan1 = HolidayCalendar(Holiday('jan1', year=2015, month=1, day=1))
jan2 = HolidayCalendar(Holiday('jan2', year=2015, month=1, day=2))


jan1.holidays()
Out[2]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2015-01-01]
Length: 1, Freq: None, Timezone: None

jan2.holidays()
Out[3]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2015-01-01]    <--------------------------- uses cached result from *other* instance!
Length: 1, Freq: None, Timezone: None
```

``` python
import numpy as np
from pandas.tseries.holiday import AbstractHolidayCalendar, Holiday

class HolidayCalendar(AbstractHolidayCalendar):
    def __init__(self, rules):
        self.rules = list(np.atleast_1d(rules))

jan1 = HolidayCalendar(Holiday('jan1', year=2015, month=1, day=1))
jan2 = HolidayCalendar(Holiday('jan2', year=2015, month=1, day=2))


jan2.holidays()
Out[2]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2015-01-02]
Length: 1, Freq: None, Timezone: None

jan1.holidays()
Out[3]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2015-01-02]    <--------------------------- uses cached result from *other* instance!
Length: 1, Freq: None, Timezone: None
```

https://github.com/pydata/pandas/blob/a72d95163b4d268012709255d7a52bbe5c1a7eb6/pandas/tseries/holiday.py#L351-L357



Discussion:
The expected use of this is that there is only one set of rules per holiday class.  Namely there is a one-to-one mapping between holiday calendars and rules.  Ideally, if you change the rules you would create a new class.  However, this isn't exactly obvious from how things work and I don't see the harm in making the cache on the instance rather than the class (see #6719) for some design discussion.


Resolving patch:
diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py
--- a/pandas/tseries/holiday.py
+++ b/pandas/tseries/holiday.py
@@ -279,7 +279,7 @@ class AbstractHolidayCalendar(object):
     rules = []
     start_date = Timestamp(datetime(1970, 1, 1))
     end_date = Timestamp(datetime(2030, 12, 31))
-    _holiday_cache = None
+    _cache = None
 
     def __init__(self, name=None, rules=None):
         """
@@ -351,14 +351,6 @@ def holidays(self, start=None, end=None, return_name=False):
         else:
             return holidays.index
 
-    @property
-    def _cache(self):
-        return self.__class__._holiday_cache
-
-    @_cache.setter
-    def _cache(self, values):
-        self.__class__._holiday_cache = values
-
     @staticmethod
     def merge_class(base, other):
         """


Test patch:
diff --git a/pandas/tseries/tests/test_holiday.py b/pandas/tseries/tests/test_holiday.py
--- a/pandas/tseries/tests/test_holiday.py
+++ b/pandas/tseries/tests/test_holiday.py
@@ -1,6 +1,7 @@
 
 from datetime import datetime
 import pandas.util.testing as tm
+from pandas import DatetimeIndex
 from pandas.tseries.holiday import (
     USFederalHolidayCalendar, USMemorialDay, USThanksgivingDay,
     nearest_workday, next_monday_or_tuesday, next_monday,
@@ -50,6 +51,29 @@ def test_calendar(self):
         self.assertEqual(list(holidays_2.to_pydatetime()),
                          self.holiday_list)
 
+    def test_calendar_caching(self):
+        # Test for issue #9552
+
+        class TestCalendar(AbstractHolidayCalendar):
+            def __init__(self, name=None, rules=None):
+                super(TestCalendar, self).__init__(
+                    name=name,
+                    rules=rules
+                )
+
+        jan1 = TestCalendar(rules=[Holiday('jan1', year=2015, month=1, day=1)])
+        jan2 = TestCalendar(rules=[Holiday('jan2', year=2015, month=1, day=2)])
+
+        tm.assert_index_equal(
+            jan1.holidays(),
+            DatetimeIndex(['01-Jan-2015'])
+        )
+        tm.assert_index_equal(
+            jan2.holidays(),
+            DatetimeIndex(['02-Jan-2015'])
+        )
+
+
 class TestHoliday(tm.TestCase):
 
     def setUp(self):