Skip to content

Commit 7d3a0fa

Browse files
committed
Merge pull request #7913 from MichaelWS/master
update to holiday to help with GH#7070
2 parents fa63e76 + 43c78d8 commit 7d3a0fa

File tree

3 files changed

+137
-2
lines changed

3 files changed

+137
-2
lines changed

doc/source/v0.15.0.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,8 @@ Enhancements
301301

302302

303303
- ``PeriodIndex`` supports ``resolution`` as the same as ``DatetimeIndex`` (:issue:`7708`)
304+
-``pandas.tseries.holiday`` has added support for additional holidays and ways to observe holidays (:issue: `7070`)
305+
-``pandas.tseries.holiday.Holiday`` now supports a list of offsets in Python3 (:issue: `7070`)
304306

305307

306308

pandas/tseries/holiday.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from pandas.compat import add_metaclass
33
from datetime import datetime, timedelta
44
from dateutil.relativedelta import MO, TU, WE, TH, FR, SA, SU
5+
from pandas.tseries.offsets import Easter, Day
56

67
def next_monday(dt):
78
"""
@@ -46,6 +47,20 @@ def sunday_to_monday(dt):
4647
return dt + timedelta(1)
4748
return dt
4849

50+
51+
def weekend_to_monday(dt):
52+
"""
53+
If holiday falls on Sunday or Saturday,
54+
use day thereafter (Monday) instead.
55+
Needed for holidays such as Christmas observation in Europe
56+
"""
57+
if dt.weekday() == 6:
58+
return dt + timedelta(1)
59+
elif dt.weekday() == 5:
60+
return dt + timedelta(2)
61+
return dt
62+
63+
4964
def nearest_workday(dt):
5065
"""
5166
If holiday falls on Saturday, use day before (Friday) instead;
@@ -57,13 +72,62 @@ def nearest_workday(dt):
5772
return dt + timedelta(1)
5873
return dt
5974

75+
76+
def next_workday(dt):
77+
"""
78+
returns next weekday used for observances
79+
"""
80+
dt += timedelta(days=1)
81+
while dt.weekday() > 4:
82+
# Mon-Fri are 0-4
83+
dt += timedelta(days=1)
84+
return dt
85+
86+
87+
def previous_workday(dt):
88+
"""
89+
returns previous weekday used for observances
90+
"""
91+
dt -= timedelta(days=1)
92+
while dt.weekday() > 4:
93+
# Mon-Fri are 0-4
94+
dt -= timedelta(days=1)
95+
return dt
96+
97+
98+
def before_nearest_workday(dt):
99+
"""
100+
returns previous workday after nearest workday
101+
"""
102+
return previous_workday(nearest_workday(dt))
103+
104+
105+
def after_nearest_workday(dt):
106+
"""
107+
returns next workday after nearest workday
108+
needed for Boxing day or multiple holidays in a series
109+
"""
110+
return next_workday(nearest_workday(dt))
111+
112+
60113
class Holiday(object):
61114
"""
62115
Class that defines a holiday with start/end dates and rules
63116
for observance.
64117
"""
65118
def __init__(self, name, year=None, month=None, day=None, offset=None,
66119
observance=None, start_date=None, end_date=None):
120+
"""
121+
Parameters
122+
----------
123+
name : str
124+
Name of the holiday , defaults to class name
125+
offset : array of pandas.tseries.offsets or
126+
class from pandas.tseries.offsets
127+
computes offset from date
128+
observance: function
129+
computes when holiday is given a pandas Timestamp
130+
"""
67131
self.name = name
68132
self.year = year
69133
self.month = month
@@ -149,7 +213,7 @@ def _apply_rule(self, dates):
149213
offsets = self.offset
150214

151215
for offset in offsets:
152-
dates = map(lambda d: d + offset, dates)
216+
dates = list(map(lambda d: d + offset, dates))
153217

154218
return dates
155219

@@ -330,6 +394,11 @@ def merge(self, other, inplace=False):
330394
offset=DateOffset(weekday=MO(3)))
331395
USPresidentsDay = Holiday('President''s Day', month=2, day=1,
332396
offset=DateOffset(weekday=MO(3)))
397+
GoodFriday = Holiday("Good Friday", month=1, day=1, offset=[Easter(), Day(-2)])
398+
399+
EasterMonday = Holiday("Easter Monday", month=1, day=1, offset=[Easter(), Day(1)])
400+
401+
333402

334403
class USFederalHolidayCalendar(AbstractHolidayCalendar):
335404
"""

pandas/tseries/tests/test_holiday.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
nearest_workday, next_monday_or_tuesday, next_monday,
77
previous_friday, sunday_to_monday, Holiday, DateOffset,
88
MO, Timestamp, AbstractHolidayCalendar, get_calendar,
9-
HolidayCalendarFactory)
9+
HolidayCalendarFactory, next_workday, previous_workday,
10+
before_nearest_workday, EasterMonday, GoodFriday,
11+
after_nearest_workday, weekend_to_monday)
12+
import nose
1013

1114
class TestCalendar(tm.TestCase):
1215

@@ -69,6 +72,37 @@ def test_usmemorialday(self):
6972
]
7073
self.assertEqual(list(holidays), holidayList)
7174

75+
def test_easter(self):
76+
holidays = EasterMonday.dates(self.start_date,
77+
self.end_date)
78+
holidayList = [Timestamp('2011-04-25 00:00:00'),
79+
Timestamp('2012-04-09 00:00:00'),
80+
Timestamp('2013-04-01 00:00:00'),
81+
Timestamp('2014-04-21 00:00:00'),
82+
Timestamp('2015-04-06 00:00:00'),
83+
Timestamp('2016-03-28 00:00:00'),
84+
Timestamp('2017-04-17 00:00:00'),
85+
Timestamp('2018-04-02 00:00:00'),
86+
Timestamp('2019-04-22 00:00:00'),
87+
Timestamp('2020-04-13 00:00:00')]
88+
89+
90+
self.assertEqual(list(holidays), holidayList)
91+
holidays = GoodFriday.dates(self.start_date,
92+
self.end_date)
93+
holidayList = [Timestamp('2011-04-22 00:00:00'),
94+
Timestamp('2012-04-06 00:00:00'),
95+
Timestamp('2013-03-29 00:00:00'),
96+
Timestamp('2014-04-18 00:00:00'),
97+
Timestamp('2015-04-03 00:00:00'),
98+
Timestamp('2016-03-25 00:00:00'),
99+
Timestamp('2017-04-14 00:00:00'),
100+
Timestamp('2018-03-30 00:00:00'),
101+
Timestamp('2019-04-19 00:00:00'),
102+
Timestamp('2020-04-10 00:00:00')]
103+
self.assertEqual(list(holidays), holidayList)
104+
105+
72106
def test_usthanksgivingday(self):
73107
holidays = USThanksgivingDay.dates(self.start_date,
74108
self.end_date)
@@ -166,3 +200,33 @@ def test_nearest_workday(self):
166200
self.assertEqual(nearest_workday(self.su), self.mo)
167201
self.assertEqual(nearest_workday(self.mo), self.mo)
168202

203+
def test_weekend_to_monday(self):
204+
self.assertEqual(weekend_to_monday(self.sa), self.mo)
205+
self.assertEqual(weekend_to_monday(self.su), self.mo)
206+
self.assertEqual(weekend_to_monday(self.mo), self.mo)
207+
208+
def test_next_workday(self):
209+
self.assertEqual(next_workday(self.sa), self.mo)
210+
self.assertEqual(next_workday(self.su), self.mo)
211+
self.assertEqual(next_workday(self.mo), self.tu)
212+
213+
def test_previous_workday(self):
214+
self.assertEqual(previous_workday(self.sa), self.fr)
215+
self.assertEqual(previous_workday(self.su), self.fr)
216+
self.assertEqual(previous_workday(self.tu), self.mo)
217+
218+
def test_before_nearest_workday(self):
219+
self.assertEqual(before_nearest_workday(self.sa), self.th)
220+
self.assertEqual(before_nearest_workday(self.su), self.fr)
221+
self.assertEqual(before_nearest_workday(self.tu), self.mo)
222+
223+
def test_after_nearest_workday(self):
224+
self.assertEqual(after_nearest_workday(self.sa), self.mo)
225+
self.assertEqual(after_nearest_workday(self.su), self.tu)
226+
self.assertEqual(after_nearest_workday(self.fr), self.mo)
227+
228+
229+
if __name__ == '__main__':
230+
nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],
231+
exit=False)
232+

0 commit comments

Comments
 (0)