test_offsets.py 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185
  1. """
  2. Tests of pandas.tseries.offsets
  3. """
  4. from __future__ import annotations
  5. from datetime import (
  6. datetime,
  7. timedelta,
  8. )
  9. import numpy as np
  10. import pytest
  11. from pandas._libs.tslibs import (
  12. NaT,
  13. Timedelta,
  14. Timestamp,
  15. conversion,
  16. timezones,
  17. )
  18. import pandas._libs.tslibs.offsets as liboffsets
  19. from pandas._libs.tslibs.offsets import (
  20. _get_offset,
  21. _offset_map,
  22. to_offset,
  23. )
  24. from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG
  25. from pandas.errors import PerformanceWarning
  26. from pandas import (
  27. DataFrame,
  28. DatetimeIndex,
  29. Series,
  30. date_range,
  31. )
  32. import pandas._testing as tm
  33. from pandas.tests.tseries.offsets.common import WeekDay
  34. from pandas.tseries import offsets
  35. from pandas.tseries.offsets import (
  36. FY5253,
  37. BDay,
  38. BMonthEnd,
  39. BusinessHour,
  40. CustomBusinessDay,
  41. CustomBusinessHour,
  42. CustomBusinessMonthBegin,
  43. CustomBusinessMonthEnd,
  44. DateOffset,
  45. Easter,
  46. FY5253Quarter,
  47. LastWeekOfMonth,
  48. MonthBegin,
  49. Nano,
  50. Tick,
  51. Week,
  52. WeekOfMonth,
  53. )
  54. _ARITHMETIC_DATE_OFFSET = [
  55. "years",
  56. "months",
  57. "weeks",
  58. "days",
  59. "hours",
  60. "minutes",
  61. "seconds",
  62. "milliseconds",
  63. "microseconds",
  64. ]
  65. def _create_offset(klass, value=1, normalize=False):
  66. # create instance from offset class
  67. if klass is FY5253:
  68. klass = klass(
  69. n=value,
  70. startingMonth=1,
  71. weekday=1,
  72. variation="last",
  73. normalize=normalize,
  74. )
  75. elif klass is FY5253Quarter:
  76. klass = klass(
  77. n=value,
  78. startingMonth=1,
  79. weekday=1,
  80. qtr_with_extra_week=1,
  81. variation="last",
  82. normalize=normalize,
  83. )
  84. elif klass is LastWeekOfMonth:
  85. klass = klass(n=value, weekday=5, normalize=normalize)
  86. elif klass is WeekOfMonth:
  87. klass = klass(n=value, week=1, weekday=5, normalize=normalize)
  88. elif klass is Week:
  89. klass = klass(n=value, weekday=5, normalize=normalize)
  90. elif klass is DateOffset:
  91. klass = klass(days=value, normalize=normalize)
  92. else:
  93. klass = klass(value, normalize=normalize)
  94. return klass
  95. @pytest.fixture(
  96. params=[
  97. getattr(offsets, o)
  98. for o in offsets.__all__
  99. if issubclass(getattr(offsets, o), liboffsets.MonthOffset)
  100. and o != "MonthOffset"
  101. ]
  102. )
  103. def month_classes(request):
  104. """
  105. Fixture for month based datetime offsets available for a time series.
  106. """
  107. return request.param
  108. @pytest.fixture(
  109. params=[
  110. getattr(offsets, o) for o in offsets.__all__ if o not in ("Tick", "BaseOffset")
  111. ]
  112. )
  113. def offset_types(request):
  114. """
  115. Fixture for all the datetime offsets available for a time series.
  116. """
  117. return request.param
  118. @pytest.fixture
  119. def dt():
  120. return Timestamp(datetime(2008, 1, 2))
  121. @pytest.fixture
  122. def expecteds():
  123. # executed value created by _create_offset
  124. # are applied to 2011/01/01 09:00 (Saturday)
  125. # used for .apply and .rollforward
  126. return {
  127. "Day": Timestamp("2011-01-02 09:00:00"),
  128. "DateOffset": Timestamp("2011-01-02 09:00:00"),
  129. "BusinessDay": Timestamp("2011-01-03 09:00:00"),
  130. "CustomBusinessDay": Timestamp("2011-01-03 09:00:00"),
  131. "CustomBusinessMonthEnd": Timestamp("2011-01-31 09:00:00"),
  132. "CustomBusinessMonthBegin": Timestamp("2011-01-03 09:00:00"),
  133. "MonthBegin": Timestamp("2011-02-01 09:00:00"),
  134. "BusinessMonthBegin": Timestamp("2011-01-03 09:00:00"),
  135. "MonthEnd": Timestamp("2011-01-31 09:00:00"),
  136. "SemiMonthEnd": Timestamp("2011-01-15 09:00:00"),
  137. "SemiMonthBegin": Timestamp("2011-01-15 09:00:00"),
  138. "BusinessMonthEnd": Timestamp("2011-01-31 09:00:00"),
  139. "YearBegin": Timestamp("2012-01-01 09:00:00"),
  140. "BYearBegin": Timestamp("2011-01-03 09:00:00"),
  141. "YearEnd": Timestamp("2011-12-31 09:00:00"),
  142. "BYearEnd": Timestamp("2011-12-30 09:00:00"),
  143. "QuarterBegin": Timestamp("2011-03-01 09:00:00"),
  144. "BQuarterBegin": Timestamp("2011-03-01 09:00:00"),
  145. "QuarterEnd": Timestamp("2011-03-31 09:00:00"),
  146. "BQuarterEnd": Timestamp("2011-03-31 09:00:00"),
  147. "BusinessHour": Timestamp("2011-01-03 10:00:00"),
  148. "CustomBusinessHour": Timestamp("2011-01-03 10:00:00"),
  149. "WeekOfMonth": Timestamp("2011-01-08 09:00:00"),
  150. "LastWeekOfMonth": Timestamp("2011-01-29 09:00:00"),
  151. "FY5253Quarter": Timestamp("2011-01-25 09:00:00"),
  152. "FY5253": Timestamp("2011-01-25 09:00:00"),
  153. "Week": Timestamp("2011-01-08 09:00:00"),
  154. "Easter": Timestamp("2011-04-24 09:00:00"),
  155. "Hour": Timestamp("2011-01-01 10:00:00"),
  156. "Minute": Timestamp("2011-01-01 09:01:00"),
  157. "Second": Timestamp("2011-01-01 09:00:01"),
  158. "Milli": Timestamp("2011-01-01 09:00:00.001000"),
  159. "Micro": Timestamp("2011-01-01 09:00:00.000001"),
  160. "Nano": Timestamp("2011-01-01T09:00:00.000000001"),
  161. }
  162. class TestCommon:
  163. def test_immutable(self, offset_types):
  164. # GH#21341 check that __setattr__ raises
  165. offset = _create_offset(offset_types)
  166. msg = "objects is not writable|DateOffset objects are immutable"
  167. with pytest.raises(AttributeError, match=msg):
  168. offset.normalize = True
  169. with pytest.raises(AttributeError, match=msg):
  170. offset.n = 91
  171. def test_return_type(self, offset_types):
  172. offset = _create_offset(offset_types)
  173. # make sure that we are returning a Timestamp
  174. result = Timestamp("20080101") + offset
  175. assert isinstance(result, Timestamp)
  176. # make sure that we are returning NaT
  177. assert NaT + offset is NaT
  178. assert offset + NaT is NaT
  179. assert NaT - offset is NaT
  180. assert (-offset)._apply(NaT) is NaT
  181. def test_offset_n(self, offset_types):
  182. offset = _create_offset(offset_types)
  183. assert offset.n == 1
  184. neg_offset = offset * -1
  185. assert neg_offset.n == -1
  186. mul_offset = offset * 3
  187. assert mul_offset.n == 3
  188. def test_offset_timedelta64_arg(self, offset_types):
  189. # check that offset._validate_n raises TypeError on a timedelt64
  190. # object
  191. off = _create_offset(offset_types)
  192. td64 = np.timedelta64(4567, "s")
  193. with pytest.raises(TypeError, match="argument must be an integer"):
  194. type(off)(n=td64, **off.kwds)
  195. def test_offset_mul_ndarray(self, offset_types):
  196. off = _create_offset(offset_types)
  197. expected = np.array([[off, off * 2], [off * 3, off * 4]])
  198. result = np.array([[1, 2], [3, 4]]) * off
  199. tm.assert_numpy_array_equal(result, expected)
  200. result = off * np.array([[1, 2], [3, 4]])
  201. tm.assert_numpy_array_equal(result, expected)
  202. def test_offset_freqstr(self, offset_types):
  203. offset = _create_offset(offset_types)
  204. freqstr = offset.freqstr
  205. if freqstr not in ("<Easter>", "<DateOffset: days=1>", "LWOM-SAT"):
  206. code = _get_offset(freqstr)
  207. assert offset.rule_code == code
  208. def _check_offsetfunc_works(self, offset, funcname, dt, expected, normalize=False):
  209. if normalize and issubclass(offset, Tick):
  210. # normalize=True disallowed for Tick subclasses GH#21427
  211. return
  212. offset_s = _create_offset(offset, normalize=normalize)
  213. func = getattr(offset_s, funcname)
  214. result = func(dt)
  215. assert isinstance(result, Timestamp)
  216. assert result == expected
  217. result = func(Timestamp(dt))
  218. assert isinstance(result, Timestamp)
  219. assert result == expected
  220. # see gh-14101
  221. ts = Timestamp(dt) + Nano(5)
  222. # test nanosecond is preserved
  223. with tm.assert_produces_warning(None):
  224. result = func(ts)
  225. assert isinstance(result, Timestamp)
  226. if normalize is False:
  227. assert result == expected + Nano(5)
  228. else:
  229. assert result == expected
  230. if isinstance(dt, np.datetime64):
  231. # test tz when input is datetime or Timestamp
  232. return
  233. for tz in [
  234. None,
  235. "UTC",
  236. "Asia/Tokyo",
  237. "US/Eastern",
  238. "dateutil/Asia/Tokyo",
  239. "dateutil/US/Pacific",
  240. ]:
  241. expected_localize = expected.tz_localize(tz)
  242. tz_obj = timezones.maybe_get_tz(tz)
  243. dt_tz = conversion.localize_pydatetime(dt, tz_obj)
  244. result = func(dt_tz)
  245. assert isinstance(result, Timestamp)
  246. assert result == expected_localize
  247. result = func(Timestamp(dt, tz=tz))
  248. assert isinstance(result, Timestamp)
  249. assert result == expected_localize
  250. # see gh-14101
  251. ts = Timestamp(dt, tz=tz) + Nano(5)
  252. # test nanosecond is preserved
  253. with tm.assert_produces_warning(None):
  254. result = func(ts)
  255. assert isinstance(result, Timestamp)
  256. if normalize is False:
  257. assert result == expected_localize + Nano(5)
  258. else:
  259. assert result == expected_localize
  260. def test_apply(self, offset_types, expecteds):
  261. sdt = datetime(2011, 1, 1, 9, 0)
  262. ndt = np.datetime64("2011-01-01 09:00")
  263. expected = expecteds[offset_types.__name__]
  264. expected_norm = Timestamp(expected.date())
  265. for dt in [sdt, ndt]:
  266. self._check_offsetfunc_works(offset_types, "_apply", dt, expected)
  267. self._check_offsetfunc_works(
  268. offset_types, "_apply", dt, expected_norm, normalize=True
  269. )
  270. def test_rollforward(self, offset_types, expecteds):
  271. expecteds = expecteds.copy()
  272. # result will not be changed if the target is on the offset
  273. no_changes = [
  274. "Day",
  275. "MonthBegin",
  276. "SemiMonthBegin",
  277. "YearBegin",
  278. "Week",
  279. "Hour",
  280. "Minute",
  281. "Second",
  282. "Milli",
  283. "Micro",
  284. "Nano",
  285. "DateOffset",
  286. ]
  287. for n in no_changes:
  288. expecteds[n] = Timestamp("2011/01/01 09:00")
  289. expecteds["BusinessHour"] = Timestamp("2011-01-03 09:00:00")
  290. expecteds["CustomBusinessHour"] = Timestamp("2011-01-03 09:00:00")
  291. # but be changed when normalize=True
  292. norm_expected = expecteds.copy()
  293. for k in norm_expected:
  294. norm_expected[k] = Timestamp(norm_expected[k].date())
  295. normalized = {
  296. "Day": Timestamp("2011-01-02 00:00:00"),
  297. "DateOffset": Timestamp("2011-01-02 00:00:00"),
  298. "MonthBegin": Timestamp("2011-02-01 00:00:00"),
  299. "SemiMonthBegin": Timestamp("2011-01-15 00:00:00"),
  300. "YearBegin": Timestamp("2012-01-01 00:00:00"),
  301. "Week": Timestamp("2011-01-08 00:00:00"),
  302. "Hour": Timestamp("2011-01-01 00:00:00"),
  303. "Minute": Timestamp("2011-01-01 00:00:00"),
  304. "Second": Timestamp("2011-01-01 00:00:00"),
  305. "Milli": Timestamp("2011-01-01 00:00:00"),
  306. "Micro": Timestamp("2011-01-01 00:00:00"),
  307. }
  308. norm_expected.update(normalized)
  309. sdt = datetime(2011, 1, 1, 9, 0)
  310. ndt = np.datetime64("2011-01-01 09:00")
  311. for dt in [sdt, ndt]:
  312. expected = expecteds[offset_types.__name__]
  313. self._check_offsetfunc_works(offset_types, "rollforward", dt, expected)
  314. expected = norm_expected[offset_types.__name__]
  315. self._check_offsetfunc_works(
  316. offset_types, "rollforward", dt, expected, normalize=True
  317. )
  318. def test_rollback(self, offset_types):
  319. expecteds = {
  320. "BusinessDay": Timestamp("2010-12-31 09:00:00"),
  321. "CustomBusinessDay": Timestamp("2010-12-31 09:00:00"),
  322. "CustomBusinessMonthEnd": Timestamp("2010-12-31 09:00:00"),
  323. "CustomBusinessMonthBegin": Timestamp("2010-12-01 09:00:00"),
  324. "BusinessMonthBegin": Timestamp("2010-12-01 09:00:00"),
  325. "MonthEnd": Timestamp("2010-12-31 09:00:00"),
  326. "SemiMonthEnd": Timestamp("2010-12-31 09:00:00"),
  327. "BusinessMonthEnd": Timestamp("2010-12-31 09:00:00"),
  328. "BYearBegin": Timestamp("2010-01-01 09:00:00"),
  329. "YearEnd": Timestamp("2010-12-31 09:00:00"),
  330. "BYearEnd": Timestamp("2010-12-31 09:00:00"),
  331. "QuarterBegin": Timestamp("2010-12-01 09:00:00"),
  332. "BQuarterBegin": Timestamp("2010-12-01 09:00:00"),
  333. "QuarterEnd": Timestamp("2010-12-31 09:00:00"),
  334. "BQuarterEnd": Timestamp("2010-12-31 09:00:00"),
  335. "BusinessHour": Timestamp("2010-12-31 17:00:00"),
  336. "CustomBusinessHour": Timestamp("2010-12-31 17:00:00"),
  337. "WeekOfMonth": Timestamp("2010-12-11 09:00:00"),
  338. "LastWeekOfMonth": Timestamp("2010-12-25 09:00:00"),
  339. "FY5253Quarter": Timestamp("2010-10-26 09:00:00"),
  340. "FY5253": Timestamp("2010-01-26 09:00:00"),
  341. "Easter": Timestamp("2010-04-04 09:00:00"),
  342. }
  343. # result will not be changed if the target is on the offset
  344. for n in [
  345. "Day",
  346. "MonthBegin",
  347. "SemiMonthBegin",
  348. "YearBegin",
  349. "Week",
  350. "Hour",
  351. "Minute",
  352. "Second",
  353. "Milli",
  354. "Micro",
  355. "Nano",
  356. "DateOffset",
  357. ]:
  358. expecteds[n] = Timestamp("2011/01/01 09:00")
  359. # but be changed when normalize=True
  360. norm_expected = expecteds.copy()
  361. for k in norm_expected:
  362. norm_expected[k] = Timestamp(norm_expected[k].date())
  363. normalized = {
  364. "Day": Timestamp("2010-12-31 00:00:00"),
  365. "DateOffset": Timestamp("2010-12-31 00:00:00"),
  366. "MonthBegin": Timestamp("2010-12-01 00:00:00"),
  367. "SemiMonthBegin": Timestamp("2010-12-15 00:00:00"),
  368. "YearBegin": Timestamp("2010-01-01 00:00:00"),
  369. "Week": Timestamp("2010-12-25 00:00:00"),
  370. "Hour": Timestamp("2011-01-01 00:00:00"),
  371. "Minute": Timestamp("2011-01-01 00:00:00"),
  372. "Second": Timestamp("2011-01-01 00:00:00"),
  373. "Milli": Timestamp("2011-01-01 00:00:00"),
  374. "Micro": Timestamp("2011-01-01 00:00:00"),
  375. }
  376. norm_expected.update(normalized)
  377. sdt = datetime(2011, 1, 1, 9, 0)
  378. ndt = np.datetime64("2011-01-01 09:00")
  379. for dt in [sdt, ndt]:
  380. expected = expecteds[offset_types.__name__]
  381. self._check_offsetfunc_works(offset_types, "rollback", dt, expected)
  382. expected = norm_expected[offset_types.__name__]
  383. self._check_offsetfunc_works(
  384. offset_types, "rollback", dt, expected, normalize=True
  385. )
  386. def test_is_on_offset(self, offset_types, expecteds):
  387. dt = expecteds[offset_types.__name__]
  388. offset_s = _create_offset(offset_types)
  389. assert offset_s.is_on_offset(dt)
  390. # when normalize=True, is_on_offset checks time is 00:00:00
  391. if issubclass(offset_types, Tick):
  392. # normalize=True disallowed for Tick subclasses GH#21427
  393. return
  394. offset_n = _create_offset(offset_types, normalize=True)
  395. assert not offset_n.is_on_offset(dt)
  396. if offset_types in (BusinessHour, CustomBusinessHour):
  397. # In default BusinessHour (9:00-17:00), normalized time
  398. # cannot be in business hour range
  399. return
  400. date = datetime(dt.year, dt.month, dt.day)
  401. assert offset_n.is_on_offset(date)
  402. def test_add(self, offset_types, tz_naive_fixture, expecteds):
  403. tz = tz_naive_fixture
  404. dt = datetime(2011, 1, 1, 9, 0)
  405. offset_s = _create_offset(offset_types)
  406. expected = expecteds[offset_types.__name__]
  407. result_dt = dt + offset_s
  408. result_ts = Timestamp(dt) + offset_s
  409. for result in [result_dt, result_ts]:
  410. assert isinstance(result, Timestamp)
  411. assert result == expected
  412. expected_localize = expected.tz_localize(tz)
  413. result = Timestamp(dt, tz=tz) + offset_s
  414. assert isinstance(result, Timestamp)
  415. assert result == expected_localize
  416. # normalize=True, disallowed for Tick subclasses GH#21427
  417. if issubclass(offset_types, Tick):
  418. return
  419. offset_s = _create_offset(offset_types, normalize=True)
  420. expected = Timestamp(expected.date())
  421. result_dt = dt + offset_s
  422. result_ts = Timestamp(dt) + offset_s
  423. for result in [result_dt, result_ts]:
  424. assert isinstance(result, Timestamp)
  425. assert result == expected
  426. expected_localize = expected.tz_localize(tz)
  427. result = Timestamp(dt, tz=tz) + offset_s
  428. assert isinstance(result, Timestamp)
  429. assert result == expected_localize
  430. def test_add_empty_datetimeindex(self, offset_types, tz_naive_fixture):
  431. # GH#12724, GH#30336
  432. offset_s = _create_offset(offset_types)
  433. dti = DatetimeIndex([], tz=tz_naive_fixture).as_unit("ns")
  434. warn = None
  435. if isinstance(
  436. offset_s,
  437. (
  438. Easter,
  439. WeekOfMonth,
  440. LastWeekOfMonth,
  441. CustomBusinessDay,
  442. BusinessHour,
  443. CustomBusinessHour,
  444. CustomBusinessMonthBegin,
  445. CustomBusinessMonthEnd,
  446. FY5253,
  447. FY5253Quarter,
  448. ),
  449. ):
  450. # We don't have an optimized apply_index
  451. warn = PerformanceWarning
  452. # stacklevel checking is slow, and we have ~800 of variants of this
  453. # test, so let's only check the stacklevel in a subset of them
  454. check_stacklevel = tz_naive_fixture is None
  455. with tm.assert_produces_warning(warn, check_stacklevel=check_stacklevel):
  456. result = dti + offset_s
  457. tm.assert_index_equal(result, dti)
  458. with tm.assert_produces_warning(warn, check_stacklevel=check_stacklevel):
  459. result = offset_s + dti
  460. tm.assert_index_equal(result, dti)
  461. dta = dti._data
  462. with tm.assert_produces_warning(warn, check_stacklevel=check_stacklevel):
  463. result = dta + offset_s
  464. tm.assert_equal(result, dta)
  465. with tm.assert_produces_warning(warn, check_stacklevel=check_stacklevel):
  466. result = offset_s + dta
  467. tm.assert_equal(result, dta)
  468. def test_pickle_roundtrip(self, offset_types):
  469. off = _create_offset(offset_types)
  470. res = tm.round_trip_pickle(off)
  471. assert off == res
  472. if type(off) is not DateOffset:
  473. for attr in off._attributes:
  474. if attr == "calendar":
  475. # np.busdaycalendar __eq__ will return False;
  476. # we check holidays and weekmask attrs so are OK
  477. continue
  478. # Make sure nothings got lost from _params (which __eq__) is based on
  479. assert getattr(off, attr) == getattr(res, attr)
  480. def test_pickle_dateoffset_odd_inputs(self):
  481. # GH#34511
  482. off = DateOffset(months=12)
  483. res = tm.round_trip_pickle(off)
  484. assert off == res
  485. base_dt = datetime(2020, 1, 1)
  486. assert base_dt + off == base_dt + res
  487. def test_offsets_hashable(self, offset_types):
  488. # GH: 37267
  489. off = _create_offset(offset_types)
  490. assert hash(off) is not None
  491. # TODO: belongs in arithmetic tests?
  492. @pytest.mark.filterwarnings(
  493. "ignore:Non-vectorized DateOffset being applied to Series or DatetimeIndex"
  494. )
  495. @pytest.mark.parametrize("unit", ["s", "ms", "us"])
  496. def test_add_dt64_ndarray_non_nano(self, offset_types, unit):
  497. # check that the result with non-nano matches nano
  498. off = _create_offset(offset_types)
  499. dti = date_range("2016-01-01", periods=35, freq="D", unit=unit)
  500. result = (dti + off)._with_freq(None)
  501. exp_unit = unit
  502. if isinstance(off, Tick) and off._creso > dti._data._creso:
  503. # cast to higher reso like we would with Timedelta scalar
  504. exp_unit = Timedelta(off).unit
  505. # TODO(GH#55564): as_unit will be unnecessary
  506. expected = DatetimeIndex([x + off for x in dti]).as_unit(exp_unit)
  507. tm.assert_index_equal(result, expected)
  508. class TestDateOffset:
  509. def setup_method(self):
  510. _offset_map.clear()
  511. def test_repr(self):
  512. repr(DateOffset())
  513. repr(DateOffset(2))
  514. repr(2 * DateOffset())
  515. repr(2 * DateOffset(months=2))
  516. def test_mul(self):
  517. assert DateOffset(2) == 2 * DateOffset(1)
  518. assert DateOffset(2) == DateOffset(1) * 2
  519. @pytest.mark.parametrize("kwd", sorted(liboffsets._relativedelta_kwds))
  520. def test_constructor(self, kwd, request):
  521. if kwd == "millisecond":
  522. request.applymarker(
  523. pytest.mark.xfail(
  524. raises=NotImplementedError,
  525. reason="Constructing DateOffset object with `millisecond` is not "
  526. "yet supported.",
  527. )
  528. )
  529. offset = DateOffset(**{kwd: 2})
  530. assert offset.kwds == {kwd: 2}
  531. assert getattr(offset, kwd) == 2
  532. def test_default_constructor(self, dt):
  533. assert (dt + DateOffset(2)) == datetime(2008, 1, 4)
  534. def test_is_anchored(self):
  535. msg = "DateOffset.is_anchored is deprecated "
  536. with tm.assert_produces_warning(FutureWarning, match=msg):
  537. assert not DateOffset(2).is_anchored()
  538. assert DateOffset(1).is_anchored()
  539. def test_copy(self):
  540. assert DateOffset(months=2).copy() == DateOffset(months=2)
  541. assert DateOffset(milliseconds=1).copy() == DateOffset(milliseconds=1)
  542. @pytest.mark.parametrize(
  543. "arithmatic_offset_type, expected",
  544. zip(
  545. _ARITHMETIC_DATE_OFFSET,
  546. [
  547. "2009-01-02",
  548. "2008-02-02",
  549. "2008-01-09",
  550. "2008-01-03",
  551. "2008-01-02 01:00:00",
  552. "2008-01-02 00:01:00",
  553. "2008-01-02 00:00:01",
  554. "2008-01-02 00:00:00.001000000",
  555. "2008-01-02 00:00:00.000001000",
  556. ],
  557. ),
  558. )
  559. def test_add(self, arithmatic_offset_type, expected, dt):
  560. assert DateOffset(**{arithmatic_offset_type: 1}) + dt == Timestamp(expected)
  561. assert dt + DateOffset(**{arithmatic_offset_type: 1}) == Timestamp(expected)
  562. @pytest.mark.parametrize(
  563. "arithmatic_offset_type, expected",
  564. zip(
  565. _ARITHMETIC_DATE_OFFSET,
  566. [
  567. "2007-01-02",
  568. "2007-12-02",
  569. "2007-12-26",
  570. "2008-01-01",
  571. "2008-01-01 23:00:00",
  572. "2008-01-01 23:59:00",
  573. "2008-01-01 23:59:59",
  574. "2008-01-01 23:59:59.999000000",
  575. "2008-01-01 23:59:59.999999000",
  576. ],
  577. ),
  578. )
  579. def test_sub(self, arithmatic_offset_type, expected, dt):
  580. assert dt - DateOffset(**{arithmatic_offset_type: 1}) == Timestamp(expected)
  581. with pytest.raises(TypeError, match="Cannot subtract datetime from offset"):
  582. DateOffset(**{arithmatic_offset_type: 1}) - dt
  583. @pytest.mark.parametrize(
  584. "arithmatic_offset_type, n, expected",
  585. zip(
  586. _ARITHMETIC_DATE_OFFSET,
  587. range(1, 10),
  588. [
  589. "2009-01-02",
  590. "2008-03-02",
  591. "2008-01-23",
  592. "2008-01-06",
  593. "2008-01-02 05:00:00",
  594. "2008-01-02 00:06:00",
  595. "2008-01-02 00:00:07",
  596. "2008-01-02 00:00:00.008000000",
  597. "2008-01-02 00:00:00.000009000",
  598. ],
  599. ),
  600. )
  601. def test_mul_add(self, arithmatic_offset_type, n, expected, dt):
  602. assert DateOffset(**{arithmatic_offset_type: 1}) * n + dt == Timestamp(expected)
  603. assert n * DateOffset(**{arithmatic_offset_type: 1}) + dt == Timestamp(expected)
  604. assert dt + DateOffset(**{arithmatic_offset_type: 1}) * n == Timestamp(expected)
  605. assert dt + n * DateOffset(**{arithmatic_offset_type: 1}) == Timestamp(expected)
  606. @pytest.mark.parametrize(
  607. "arithmatic_offset_type, n, expected",
  608. zip(
  609. _ARITHMETIC_DATE_OFFSET,
  610. range(1, 10),
  611. [
  612. "2007-01-02",
  613. "2007-11-02",
  614. "2007-12-12",
  615. "2007-12-29",
  616. "2008-01-01 19:00:00",
  617. "2008-01-01 23:54:00",
  618. "2008-01-01 23:59:53",
  619. "2008-01-01 23:59:59.992000000",
  620. "2008-01-01 23:59:59.999991000",
  621. ],
  622. ),
  623. )
  624. def test_mul_sub(self, arithmatic_offset_type, n, expected, dt):
  625. assert dt - DateOffset(**{arithmatic_offset_type: 1}) * n == Timestamp(expected)
  626. assert dt - n * DateOffset(**{arithmatic_offset_type: 1}) == Timestamp(expected)
  627. def test_leap_year(self):
  628. d = datetime(2008, 1, 31)
  629. assert (d + DateOffset(months=1)) == datetime(2008, 2, 29)
  630. def test_eq(self):
  631. offset1 = DateOffset(days=1)
  632. offset2 = DateOffset(days=365)
  633. assert offset1 != offset2
  634. assert DateOffset(milliseconds=3) != DateOffset(milliseconds=7)
  635. @pytest.mark.parametrize(
  636. "offset_kwargs, expected_arg",
  637. [
  638. ({"microseconds": 1, "milliseconds": 1}, "2022-01-01 00:00:00.001001"),
  639. ({"seconds": 1, "milliseconds": 1}, "2022-01-01 00:00:01.001"),
  640. ({"minutes": 1, "milliseconds": 1}, "2022-01-01 00:01:00.001"),
  641. ({"hours": 1, "milliseconds": 1}, "2022-01-01 01:00:00.001"),
  642. ({"days": 1, "milliseconds": 1}, "2022-01-02 00:00:00.001"),
  643. ({"weeks": 1, "milliseconds": 1}, "2022-01-08 00:00:00.001"),
  644. ({"months": 1, "milliseconds": 1}, "2022-02-01 00:00:00.001"),
  645. ({"years": 1, "milliseconds": 1}, "2023-01-01 00:00:00.001"),
  646. ],
  647. )
  648. def test_milliseconds_combination(self, offset_kwargs, expected_arg):
  649. # GH 49897
  650. offset = DateOffset(**offset_kwargs)
  651. ts = Timestamp("2022-01-01")
  652. result = ts + offset
  653. expected = Timestamp(expected_arg)
  654. assert result == expected
  655. def test_offset_invalid_arguments(self):
  656. msg = "^Invalid argument/s or bad combination of arguments"
  657. with pytest.raises(ValueError, match=msg):
  658. DateOffset(picoseconds=1)
  659. class TestOffsetNames:
  660. def test_get_offset_name(self):
  661. assert BDay().freqstr == "B"
  662. assert BDay(2).freqstr == "2B"
  663. assert BMonthEnd().freqstr == "BME"
  664. assert Week(weekday=0).freqstr == "W-MON"
  665. assert Week(weekday=1).freqstr == "W-TUE"
  666. assert Week(weekday=2).freqstr == "W-WED"
  667. assert Week(weekday=3).freqstr == "W-THU"
  668. assert Week(weekday=4).freqstr == "W-FRI"
  669. assert LastWeekOfMonth(weekday=WeekDay.SUN).freqstr == "LWOM-SUN"
  670. def test_get_offset():
  671. with pytest.raises(ValueError, match=INVALID_FREQ_ERR_MSG):
  672. _get_offset("gibberish")
  673. with pytest.raises(ValueError, match=INVALID_FREQ_ERR_MSG):
  674. _get_offset("QS-JAN-B")
  675. pairs = [
  676. ("B", BDay()),
  677. ("b", BDay()),
  678. ("bme", BMonthEnd()),
  679. ("Bme", BMonthEnd()),
  680. ("W-MON", Week(weekday=0)),
  681. ("W-TUE", Week(weekday=1)),
  682. ("W-WED", Week(weekday=2)),
  683. ("W-THU", Week(weekday=3)),
  684. ("W-FRI", Week(weekday=4)),
  685. ]
  686. for name, expected in pairs:
  687. offset = _get_offset(name)
  688. assert offset == expected, (
  689. f"Expected {repr(name)} to yield {repr(expected)} "
  690. f"(actual: {repr(offset)})"
  691. )
  692. def test_get_offset_legacy():
  693. pairs = [("w@Sat", Week(weekday=5))]
  694. for name, expected in pairs:
  695. with pytest.raises(ValueError, match=INVALID_FREQ_ERR_MSG):
  696. _get_offset(name)
  697. class TestOffsetAliases:
  698. def setup_method(self):
  699. _offset_map.clear()
  700. def test_alias_equality(self):
  701. for k, v in _offset_map.items():
  702. if v is None:
  703. continue
  704. assert k == v.copy()
  705. def test_rule_code(self):
  706. lst = ["ME", "MS", "BME", "BMS", "D", "B", "h", "min", "s", "ms", "us"]
  707. for k in lst:
  708. assert k == _get_offset(k).rule_code
  709. # should be cached - this is kind of an internals test...
  710. assert k in _offset_map
  711. assert k == (_get_offset(k) * 3).rule_code
  712. suffix_lst = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"]
  713. base = "W"
  714. for v in suffix_lst:
  715. alias = "-".join([base, v])
  716. assert alias == _get_offset(alias).rule_code
  717. assert alias == (_get_offset(alias) * 5).rule_code
  718. suffix_lst = [
  719. "JAN",
  720. "FEB",
  721. "MAR",
  722. "APR",
  723. "MAY",
  724. "JUN",
  725. "JUL",
  726. "AUG",
  727. "SEP",
  728. "OCT",
  729. "NOV",
  730. "DEC",
  731. ]
  732. base_lst = ["YE", "YS", "BYE", "BYS", "QE", "QS", "BQE", "BQS"]
  733. for base in base_lst:
  734. for v in suffix_lst:
  735. alias = "-".join([base, v])
  736. assert alias == _get_offset(alias).rule_code
  737. assert alias == (_get_offset(alias) * 5).rule_code
  738. def test_freq_offsets():
  739. off = BDay(1, offset=timedelta(0, 1800))
  740. assert off.freqstr == "B+30Min"
  741. off = BDay(1, offset=timedelta(0, -1800))
  742. assert off.freqstr == "B-30Min"
  743. class TestReprNames:
  744. def test_str_for_named_is_name(self):
  745. # look at all the amazing combinations!
  746. month_prefixes = ["YE", "YS", "BYE", "BYS", "QE", "BQE", "BQS", "QS"]
  747. names = [
  748. prefix + "-" + month
  749. for prefix in month_prefixes
  750. for month in [
  751. "JAN",
  752. "FEB",
  753. "MAR",
  754. "APR",
  755. "MAY",
  756. "JUN",
  757. "JUL",
  758. "AUG",
  759. "SEP",
  760. "OCT",
  761. "NOV",
  762. "DEC",
  763. ]
  764. ]
  765. days = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"]
  766. names += ["W-" + day for day in days]
  767. names += ["WOM-" + week + day for week in ("1", "2", "3", "4") for day in days]
  768. _offset_map.clear()
  769. for name in names:
  770. offset = _get_offset(name)
  771. assert offset.freqstr == name
  772. # ---------------------------------------------------------------------
  773. def test_valid_default_arguments(offset_types):
  774. # GH#19142 check that the calling the constructors without passing
  775. # any keyword arguments produce valid offsets
  776. cls = offset_types
  777. cls()
  778. @pytest.mark.parametrize("kwd", sorted(liboffsets._relativedelta_kwds))
  779. def test_valid_month_attributes(kwd, month_classes):
  780. # GH#18226
  781. cls = month_classes
  782. # check that we cannot create e.g. MonthEnd(weeks=3)
  783. msg = rf"__init__\(\) got an unexpected keyword argument '{kwd}'"
  784. with pytest.raises(TypeError, match=msg):
  785. cls(**{kwd: 3})
  786. def test_month_offset_name(month_classes):
  787. # GH#33757 off.name with n != 1 should not raise AttributeError
  788. obj = month_classes(1)
  789. obj2 = month_classes(2)
  790. assert obj2.name == obj.name
  791. @pytest.mark.parametrize("kwd", sorted(liboffsets._relativedelta_kwds))
  792. def test_valid_relativedelta_kwargs(kwd, request):
  793. if kwd == "millisecond":
  794. request.applymarker(
  795. pytest.mark.xfail(
  796. raises=NotImplementedError,
  797. reason="Constructing DateOffset object with `millisecond` is not "
  798. "yet supported.",
  799. )
  800. )
  801. # Check that all the arguments specified in liboffsets._relativedelta_kwds
  802. # are in fact valid relativedelta keyword args
  803. DateOffset(**{kwd: 1})
  804. @pytest.mark.parametrize("kwd", sorted(liboffsets._relativedelta_kwds))
  805. def test_valid_tick_attributes(kwd, tick_classes):
  806. # GH#18226
  807. cls = tick_classes
  808. # check that we cannot create e.g. Hour(weeks=3)
  809. msg = rf"__init__\(\) got an unexpected keyword argument '{kwd}'"
  810. with pytest.raises(TypeError, match=msg):
  811. cls(**{kwd: 3})
  812. def test_validate_n_error():
  813. with pytest.raises(TypeError, match="argument must be an integer"):
  814. DateOffset(n="Doh!")
  815. with pytest.raises(TypeError, match="argument must be an integer"):
  816. MonthBegin(n=timedelta(1))
  817. with pytest.raises(TypeError, match="argument must be an integer"):
  818. BDay(n=np.array([1, 2], dtype=np.int64))
  819. def test_require_integers(offset_types):
  820. cls = offset_types
  821. with pytest.raises(ValueError, match="argument must be an integer"):
  822. cls(n=1.5)
  823. def test_tick_normalize_raises(tick_classes):
  824. # check that trying to create a Tick object with normalize=True raises
  825. # GH#21427
  826. cls = tick_classes
  827. msg = "Tick offset with `normalize=True` are not allowed."
  828. with pytest.raises(ValueError, match=msg):
  829. cls(n=3, normalize=True)
  830. @pytest.mark.parametrize(
  831. "offset_kwargs, expected_arg",
  832. [
  833. ({"nanoseconds": 1}, "1970-01-01 00:00:00.000000001"),
  834. ({"nanoseconds": 5}, "1970-01-01 00:00:00.000000005"),
  835. ({"nanoseconds": -1}, "1969-12-31 23:59:59.999999999"),
  836. ({"microseconds": 1}, "1970-01-01 00:00:00.000001"),
  837. ({"microseconds": -1}, "1969-12-31 23:59:59.999999"),
  838. ({"seconds": 1}, "1970-01-01 00:00:01"),
  839. ({"seconds": -1}, "1969-12-31 23:59:59"),
  840. ({"minutes": 1}, "1970-01-01 00:01:00"),
  841. ({"minutes": -1}, "1969-12-31 23:59:00"),
  842. ({"hours": 1}, "1970-01-01 01:00:00"),
  843. ({"hours": -1}, "1969-12-31 23:00:00"),
  844. ({"days": 1}, "1970-01-02 00:00:00"),
  845. ({"days": -1}, "1969-12-31 00:00:00"),
  846. ({"weeks": 1}, "1970-01-08 00:00:00"),
  847. ({"weeks": -1}, "1969-12-25 00:00:00"),
  848. ({"months": 1}, "1970-02-01 00:00:00"),
  849. ({"months": -1}, "1969-12-01 00:00:00"),
  850. ({"years": 1}, "1971-01-01 00:00:00"),
  851. ({"years": -1}, "1969-01-01 00:00:00"),
  852. ],
  853. )
  854. def test_dateoffset_add_sub(offset_kwargs, expected_arg):
  855. offset = DateOffset(**offset_kwargs)
  856. ts = Timestamp(0)
  857. result = ts + offset
  858. expected = Timestamp(expected_arg)
  859. assert result == expected
  860. result -= offset
  861. assert result == ts
  862. result = offset + ts
  863. assert result == expected
  864. def test_dateoffset_add_sub_timestamp_with_nano():
  865. offset = DateOffset(minutes=2, nanoseconds=9)
  866. ts = Timestamp(4)
  867. result = ts + offset
  868. expected = Timestamp("1970-01-01 00:02:00.000000013")
  869. assert result == expected
  870. result -= offset
  871. assert result == ts
  872. result = offset + ts
  873. assert result == expected
  874. offset2 = DateOffset(minutes=2, nanoseconds=9, hour=1)
  875. assert offset2._use_relativedelta
  876. with tm.assert_produces_warning(None):
  877. # no warning about Discarding nonzero nanoseconds
  878. result2 = ts + offset2
  879. expected2 = Timestamp("1970-01-01 01:02:00.000000013")
  880. assert result2 == expected2
  881. @pytest.mark.parametrize(
  882. "attribute",
  883. [
  884. "hours",
  885. "days",
  886. "weeks",
  887. "months",
  888. "years",
  889. ],
  890. )
  891. def test_dateoffset_immutable(attribute):
  892. offset = DateOffset(**{attribute: 0})
  893. msg = "DateOffset objects are immutable"
  894. with pytest.raises(AttributeError, match=msg):
  895. setattr(offset, attribute, 5)
  896. def test_dateoffset_misc():
  897. oset = offsets.DateOffset(months=2, days=4)
  898. # it works
  899. oset.freqstr
  900. assert not offsets.DateOffset(months=2) == 2
  901. @pytest.mark.parametrize("n", [-1, 1, 3])
  902. def test_construct_int_arg_no_kwargs_assumed_days(n):
  903. # GH 45890, 45643
  904. offset = DateOffset(n)
  905. assert offset._offset == timedelta(1)
  906. result = Timestamp(2022, 1, 2) + offset
  907. expected = Timestamp(2022, 1, 2 + n)
  908. assert result == expected
  909. @pytest.mark.parametrize(
  910. "offset, expected",
  911. [
  912. (
  913. DateOffset(minutes=7, nanoseconds=18),
  914. Timestamp("2022-01-01 00:07:00.000000018"),
  915. ),
  916. (DateOffset(nanoseconds=3), Timestamp("2022-01-01 00:00:00.000000003")),
  917. ],
  918. )
  919. def test_dateoffset_add_sub_timestamp_series_with_nano(offset, expected):
  920. # GH 47856
  921. start_time = Timestamp("2022-01-01")
  922. teststamp = start_time
  923. testseries = Series([start_time])
  924. testseries = testseries + offset
  925. assert testseries[0] == expected
  926. testseries -= offset
  927. assert testseries[0] == teststamp
  928. testseries = offset + testseries
  929. assert testseries[0] == expected
  930. @pytest.mark.parametrize(
  931. "n_months, scaling_factor, start_timestamp, expected_timestamp",
  932. [
  933. (1, 2, "2020-01-30", "2020-03-30"),
  934. (2, 1, "2020-01-30", "2020-03-30"),
  935. (1, 0, "2020-01-30", "2020-01-30"),
  936. (2, 0, "2020-01-30", "2020-01-30"),
  937. (1, -1, "2020-01-30", "2019-12-30"),
  938. (2, -1, "2020-01-30", "2019-11-30"),
  939. ],
  940. )
  941. def test_offset_multiplication(
  942. n_months, scaling_factor, start_timestamp, expected_timestamp
  943. ):
  944. # GH 47953
  945. mo1 = DateOffset(months=n_months)
  946. startscalar = Timestamp(start_timestamp)
  947. startarray = Series([startscalar])
  948. resultscalar = startscalar + (mo1 * scaling_factor)
  949. resultarray = startarray + (mo1 * scaling_factor)
  950. expectedscalar = Timestamp(expected_timestamp)
  951. expectedarray = Series([expectedscalar])
  952. assert resultscalar == expectedscalar
  953. tm.assert_series_equal(resultarray, expectedarray)
  954. def test_dateoffset_operations_on_dataframes():
  955. # GH 47953
  956. df = DataFrame({"T": [Timestamp("2019-04-30")], "D": [DateOffset(months=1)]})
  957. frameresult1 = df["T"] + 26 * df["D"]
  958. df2 = DataFrame(
  959. {
  960. "T": [Timestamp("2019-04-30"), Timestamp("2019-04-30")],
  961. "D": [DateOffset(months=1), DateOffset(months=1)],
  962. }
  963. )
  964. expecteddate = Timestamp("2021-06-30")
  965. with tm.assert_produces_warning(PerformanceWarning):
  966. frameresult2 = df2["T"] + 26 * df2["D"]
  967. assert frameresult1[0] == expecteddate
  968. assert frameresult2[0] == expecteddate
  969. def test_is_yqm_start_end():
  970. freq_m = to_offset("ME")
  971. bm = to_offset("BME")
  972. qfeb = to_offset("QE-FEB")
  973. qsfeb = to_offset("QS-FEB")
  974. bq = to_offset("BQE")
  975. bqs_apr = to_offset("BQS-APR")
  976. as_nov = to_offset("YS-NOV")
  977. tests = [
  978. (freq_m.is_month_start(Timestamp("2013-06-01")), 1),
  979. (bm.is_month_start(Timestamp("2013-06-01")), 0),
  980. (freq_m.is_month_start(Timestamp("2013-06-03")), 0),
  981. (bm.is_month_start(Timestamp("2013-06-03")), 1),
  982. (qfeb.is_month_end(Timestamp("2013-02-28")), 1),
  983. (qfeb.is_quarter_end(Timestamp("2013-02-28")), 1),
  984. (qfeb.is_year_end(Timestamp("2013-02-28")), 1),
  985. (qfeb.is_month_start(Timestamp("2013-03-01")), 1),
  986. (qfeb.is_quarter_start(Timestamp("2013-03-01")), 1),
  987. (qfeb.is_year_start(Timestamp("2013-03-01")), 1),
  988. (qsfeb.is_month_end(Timestamp("2013-03-31")), 1),
  989. (qsfeb.is_quarter_end(Timestamp("2013-03-31")), 0),
  990. (qsfeb.is_year_end(Timestamp("2013-03-31")), 0),
  991. (qsfeb.is_month_start(Timestamp("2013-02-01")), 1),
  992. (qsfeb.is_quarter_start(Timestamp("2013-02-01")), 1),
  993. (qsfeb.is_year_start(Timestamp("2013-02-01")), 1),
  994. (bq.is_month_end(Timestamp("2013-06-30")), 0),
  995. (bq.is_quarter_end(Timestamp("2013-06-30")), 0),
  996. (bq.is_year_end(Timestamp("2013-06-30")), 0),
  997. (bq.is_month_end(Timestamp("2013-06-28")), 1),
  998. (bq.is_quarter_end(Timestamp("2013-06-28")), 1),
  999. (bq.is_year_end(Timestamp("2013-06-28")), 0),
  1000. (bqs_apr.is_month_end(Timestamp("2013-06-30")), 0),
  1001. (bqs_apr.is_quarter_end(Timestamp("2013-06-30")), 0),
  1002. (bqs_apr.is_year_end(Timestamp("2013-06-30")), 0),
  1003. (bqs_apr.is_month_end(Timestamp("2013-06-28")), 1),
  1004. (bqs_apr.is_quarter_end(Timestamp("2013-06-28")), 1),
  1005. (bqs_apr.is_year_end(Timestamp("2013-03-29")), 1),
  1006. (as_nov.is_year_start(Timestamp("2013-11-01")), 1),
  1007. (as_nov.is_year_end(Timestamp("2013-10-31")), 1),
  1008. (Timestamp("2012-02-01").days_in_month, 29),
  1009. (Timestamp("2013-02-01").days_in_month, 28),
  1010. ]
  1011. for ts, value in tests:
  1012. assert ts == value