test_holiday.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. from datetime import datetime
  2. import pytest
  3. from pytz import utc
  4. from pandas import DatetimeIndex
  5. import pandas._testing as tm
  6. from pandas.tseries.holiday import (
  7. MO,
  8. SA,
  9. AbstractHolidayCalendar,
  10. DateOffset,
  11. EasterMonday,
  12. GoodFriday,
  13. Holiday,
  14. HolidayCalendarFactory,
  15. Timestamp,
  16. USColumbusDay,
  17. USLaborDay,
  18. USMartinLutherKingJr,
  19. USMemorialDay,
  20. USPresidentsDay,
  21. USThanksgivingDay,
  22. get_calendar,
  23. next_monday,
  24. )
  25. @pytest.mark.parametrize(
  26. "holiday,start_date,end_date,expected",
  27. [
  28. (
  29. USMemorialDay,
  30. datetime(2011, 1, 1),
  31. datetime(2020, 12, 31),
  32. [
  33. datetime(2011, 5, 30),
  34. datetime(2012, 5, 28),
  35. datetime(2013, 5, 27),
  36. datetime(2014, 5, 26),
  37. datetime(2015, 5, 25),
  38. datetime(2016, 5, 30),
  39. datetime(2017, 5, 29),
  40. datetime(2018, 5, 28),
  41. datetime(2019, 5, 27),
  42. datetime(2020, 5, 25),
  43. ],
  44. ),
  45. (
  46. Holiday("July 4th Eve", month=7, day=3),
  47. "2001-01-01",
  48. "2003-03-03",
  49. [Timestamp("2001-07-03 00:00:00"), Timestamp("2002-07-03 00:00:00")],
  50. ),
  51. (
  52. Holiday("July 4th Eve", month=7, day=3, days_of_week=(0, 1, 2, 3)),
  53. "2001-01-01",
  54. "2008-03-03",
  55. [
  56. Timestamp("2001-07-03 00:00:00"),
  57. Timestamp("2002-07-03 00:00:00"),
  58. Timestamp("2003-07-03 00:00:00"),
  59. Timestamp("2006-07-03 00:00:00"),
  60. Timestamp("2007-07-03 00:00:00"),
  61. ],
  62. ),
  63. (
  64. EasterMonday,
  65. datetime(2011, 1, 1),
  66. datetime(2020, 12, 31),
  67. [
  68. Timestamp("2011-04-25 00:00:00"),
  69. Timestamp("2012-04-09 00:00:00"),
  70. Timestamp("2013-04-01 00:00:00"),
  71. Timestamp("2014-04-21 00:00:00"),
  72. Timestamp("2015-04-06 00:00:00"),
  73. Timestamp("2016-03-28 00:00:00"),
  74. Timestamp("2017-04-17 00:00:00"),
  75. Timestamp("2018-04-02 00:00:00"),
  76. Timestamp("2019-04-22 00:00:00"),
  77. Timestamp("2020-04-13 00:00:00"),
  78. ],
  79. ),
  80. (
  81. GoodFriday,
  82. datetime(2011, 1, 1),
  83. datetime(2020, 12, 31),
  84. [
  85. Timestamp("2011-04-22 00:00:00"),
  86. Timestamp("2012-04-06 00:00:00"),
  87. Timestamp("2013-03-29 00:00:00"),
  88. Timestamp("2014-04-18 00:00:00"),
  89. Timestamp("2015-04-03 00:00:00"),
  90. Timestamp("2016-03-25 00:00:00"),
  91. Timestamp("2017-04-14 00:00:00"),
  92. Timestamp("2018-03-30 00:00:00"),
  93. Timestamp("2019-04-19 00:00:00"),
  94. Timestamp("2020-04-10 00:00:00"),
  95. ],
  96. ),
  97. (
  98. USThanksgivingDay,
  99. datetime(2011, 1, 1),
  100. datetime(2020, 12, 31),
  101. [
  102. datetime(2011, 11, 24),
  103. datetime(2012, 11, 22),
  104. datetime(2013, 11, 28),
  105. datetime(2014, 11, 27),
  106. datetime(2015, 11, 26),
  107. datetime(2016, 11, 24),
  108. datetime(2017, 11, 23),
  109. datetime(2018, 11, 22),
  110. datetime(2019, 11, 28),
  111. datetime(2020, 11, 26),
  112. ],
  113. ),
  114. ],
  115. )
  116. def test_holiday_dates(holiday, start_date, end_date, expected):
  117. assert list(holiday.dates(start_date, end_date)) == expected
  118. # Verify that timezone info is preserved.
  119. assert list(
  120. holiday.dates(
  121. utc.localize(Timestamp(start_date)), utc.localize(Timestamp(end_date))
  122. )
  123. ) == [utc.localize(dt) for dt in expected]
  124. @pytest.mark.parametrize(
  125. "holiday,start,expected",
  126. [
  127. (USMemorialDay, datetime(2015, 7, 1), []),
  128. (USMemorialDay, "2015-05-25", [Timestamp("2015-05-25")]),
  129. (USLaborDay, datetime(2015, 7, 1), []),
  130. (USLaborDay, "2015-09-07", [Timestamp("2015-09-07")]),
  131. (USColumbusDay, datetime(2015, 7, 1), []),
  132. (USColumbusDay, "2015-10-12", [Timestamp("2015-10-12")]),
  133. (USThanksgivingDay, datetime(2015, 7, 1), []),
  134. (USThanksgivingDay, "2015-11-26", [Timestamp("2015-11-26")]),
  135. (USMartinLutherKingJr, datetime(2015, 7, 1), []),
  136. (USMartinLutherKingJr, "2015-01-19", [Timestamp("2015-01-19")]),
  137. (USPresidentsDay, datetime(2015, 7, 1), []),
  138. (USPresidentsDay, "2015-02-16", [Timestamp("2015-02-16")]),
  139. (GoodFriday, datetime(2015, 7, 1), []),
  140. (GoodFriday, "2015-04-03", [Timestamp("2015-04-03")]),
  141. (EasterMonday, "2015-04-06", [Timestamp("2015-04-06")]),
  142. (EasterMonday, datetime(2015, 7, 1), []),
  143. (EasterMonday, "2015-04-05", []),
  144. ("New Year's Day", "2015-01-01", [Timestamp("2015-01-01")]),
  145. ("New Year's Day", "2010-12-31", [Timestamp("2010-12-31")]),
  146. ("New Year's Day", datetime(2015, 7, 1), []),
  147. ("New Year's Day", "2011-01-01", []),
  148. ("Independence Day", "2015-07-03", [Timestamp("2015-07-03")]),
  149. ("Independence Day", datetime(2015, 7, 1), []),
  150. ("Independence Day", "2015-07-04", []),
  151. ("Veterans Day", "2012-11-12", [Timestamp("2012-11-12")]),
  152. ("Veterans Day", datetime(2015, 7, 1), []),
  153. ("Veterans Day", "2012-11-11", []),
  154. ("Christmas Day", "2011-12-26", [Timestamp("2011-12-26")]),
  155. ("Christmas Day", datetime(2015, 7, 1), []),
  156. ("Christmas Day", "2011-12-25", []),
  157. ("Juneteenth National Independence Day", "2020-06-19", []),
  158. (
  159. "Juneteenth National Independence Day",
  160. "2021-06-18",
  161. [Timestamp("2021-06-18")],
  162. ),
  163. ("Juneteenth National Independence Day", "2022-06-19", []),
  164. (
  165. "Juneteenth National Independence Day",
  166. "2022-06-20",
  167. [Timestamp("2022-06-20")],
  168. ),
  169. ],
  170. )
  171. def test_holidays_within_dates(holiday, start, expected):
  172. # see gh-11477
  173. #
  174. # Fix holiday behavior where holiday.dates returned dates outside
  175. # start/end date, or observed rules could not be applied because the
  176. # holiday was not in the original date range (e.g., 7/4/2015 -> 7/3/2015).
  177. if isinstance(holiday, str):
  178. calendar = get_calendar("USFederalHolidayCalendar")
  179. holiday = calendar.rule_from_name(holiday)
  180. assert list(holiday.dates(start, start)) == expected
  181. # Verify that timezone info is preserved.
  182. assert list(
  183. holiday.dates(utc.localize(Timestamp(start)), utc.localize(Timestamp(start)))
  184. ) == [utc.localize(dt) for dt in expected]
  185. @pytest.mark.parametrize(
  186. "transform", [lambda x: x.strftime("%Y-%m-%d"), lambda x: Timestamp(x)]
  187. )
  188. def test_argument_types(transform):
  189. start_date = datetime(2011, 1, 1)
  190. end_date = datetime(2020, 12, 31)
  191. holidays = USThanksgivingDay.dates(start_date, end_date)
  192. holidays2 = USThanksgivingDay.dates(transform(start_date), transform(end_date))
  193. tm.assert_index_equal(holidays, holidays2)
  194. @pytest.mark.parametrize(
  195. "name,kwargs",
  196. [
  197. ("One-Time", {"year": 2012, "month": 5, "day": 28}),
  198. (
  199. "Range",
  200. {
  201. "month": 5,
  202. "day": 28,
  203. "start_date": datetime(2012, 1, 1),
  204. "end_date": datetime(2012, 12, 31),
  205. "offset": DateOffset(weekday=MO(1)),
  206. },
  207. ),
  208. ],
  209. )
  210. def test_special_holidays(name, kwargs):
  211. base_date = [datetime(2012, 5, 28)]
  212. holiday = Holiday(name, **kwargs)
  213. start_date = datetime(2011, 1, 1)
  214. end_date = datetime(2020, 12, 31)
  215. assert base_date == holiday.dates(start_date, end_date)
  216. def test_get_calendar():
  217. class TestCalendar(AbstractHolidayCalendar):
  218. rules = []
  219. calendar = get_calendar("TestCalendar")
  220. assert TestCalendar == type(calendar)
  221. def test_factory():
  222. class_1 = HolidayCalendarFactory(
  223. "MemorialDay", AbstractHolidayCalendar, USMemorialDay
  224. )
  225. class_2 = HolidayCalendarFactory(
  226. "Thanksgiving", AbstractHolidayCalendar, USThanksgivingDay
  227. )
  228. class_3 = HolidayCalendarFactory("Combined", class_1, class_2)
  229. assert len(class_1.rules) == 1
  230. assert len(class_2.rules) == 1
  231. assert len(class_3.rules) == 2
  232. def test_both_offset_observance_raises():
  233. # see gh-10217
  234. msg = "Cannot use both offset and observance"
  235. with pytest.raises(NotImplementedError, match=msg):
  236. Holiday(
  237. "Cyber Monday",
  238. month=11,
  239. day=1,
  240. offset=[DateOffset(weekday=SA(4))],
  241. observance=next_monday,
  242. )
  243. def test_half_open_interval_with_observance():
  244. # Prompted by GH 49075
  245. # Check for holidays that have a half-open date interval where
  246. # they have either a start_date or end_date defined along
  247. # with a defined observance pattern to make sure that the return type
  248. # for Holiday.dates() remains consistent before & after the year that
  249. # marks the 'edge' of the half-open date interval.
  250. holiday_1 = Holiday(
  251. "Arbitrary Holiday - start 2022-03-14",
  252. start_date=datetime(2022, 3, 14),
  253. month=3,
  254. day=14,
  255. observance=next_monday,
  256. )
  257. holiday_2 = Holiday(
  258. "Arbitrary Holiday 2 - end 2022-03-20",
  259. end_date=datetime(2022, 3, 20),
  260. month=3,
  261. day=20,
  262. observance=next_monday,
  263. )
  264. class TestHolidayCalendar(AbstractHolidayCalendar):
  265. rules = [
  266. USMartinLutherKingJr,
  267. holiday_1,
  268. holiday_2,
  269. USLaborDay,
  270. ]
  271. start = Timestamp("2022-08-01")
  272. end = Timestamp("2022-08-31")
  273. year_offset = DateOffset(years=5)
  274. expected_results = DatetimeIndex([], dtype="datetime64[ns]", freq=None)
  275. test_cal = TestHolidayCalendar()
  276. date_interval_low = test_cal.holidays(start - year_offset, end - year_offset)
  277. date_window_edge = test_cal.holidays(start, end)
  278. date_interval_high = test_cal.holidays(start + year_offset, end + year_offset)
  279. tm.assert_index_equal(date_interval_low, expected_results)
  280. tm.assert_index_equal(date_window_edge, expected_results)
  281. tm.assert_index_equal(date_interval_high, expected_results)