1from datetime import date, datetime, time as dt_time, timedelta 2from typing import Dict, List, Optional, Tuple, Type 3 4from dateutil.tz import tzlocal 5import numpy as np 6import pytest 7 8from pandas._libs.tslibs import ( 9 NaT, 10 OutOfBoundsDatetime, 11 Timestamp, 12 conversion, 13 timezones, 14) 15import pandas._libs.tslibs.offsets as liboffsets 16from pandas._libs.tslibs.offsets import ApplyTypeError, _get_offset, _offset_map 17from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG 18from pandas.compat import IS64 19from pandas.compat.numpy import np_datetime64_compat 20from pandas.errors import PerformanceWarning 21 22import pandas._testing as tm 23from pandas.core.indexes.datetimes import DatetimeIndex, date_range 24from pandas.core.series import Series 25 26from pandas.io.pickle import read_pickle 27from pandas.tseries.holiday import USFederalHolidayCalendar 28import pandas.tseries.offsets as offsets 29from pandas.tseries.offsets import ( 30 FY5253, 31 BaseOffset, 32 BDay, 33 BMonthBegin, 34 BMonthEnd, 35 BQuarterBegin, 36 BQuarterEnd, 37 BusinessHour, 38 BYearBegin, 39 BYearEnd, 40 CBMonthBegin, 41 CBMonthEnd, 42 CDay, 43 CustomBusinessDay, 44 CustomBusinessHour, 45 CustomBusinessMonthBegin, 46 CustomBusinessMonthEnd, 47 DateOffset, 48 Day, 49 Easter, 50 FY5253Quarter, 51 LastWeekOfMonth, 52 MonthBegin, 53 MonthEnd, 54 Nano, 55 QuarterBegin, 56 QuarterEnd, 57 SemiMonthBegin, 58 SemiMonthEnd, 59 Tick, 60 Week, 61 WeekOfMonth, 62 YearBegin, 63 YearEnd, 64) 65 66from .common import assert_is_on_offset, assert_offset_equal 67 68 69class WeekDay: 70 # TODO: Remove: This is not used outside of tests 71 MON = 0 72 TUE = 1 73 WED = 2 74 THU = 3 75 FRI = 4 76 SAT = 5 77 SUN = 6 78 79 80##### 81# DateOffset Tests 82##### 83_ApplyCases = List[Tuple[BaseOffset, Dict[datetime, datetime]]] 84 85 86class Base: 87 _offset: Optional[Type[DateOffset]] = None 88 d = Timestamp(datetime(2008, 1, 2)) 89 90 timezones = [ 91 None, 92 "UTC", 93 "Asia/Tokyo", 94 "US/Eastern", 95 "dateutil/Asia/Tokyo", 96 "dateutil/US/Pacific", 97 ] 98 99 def _get_offset(self, klass, value=1, normalize=False): 100 # create instance from offset class 101 if klass is FY5253: 102 klass = klass( 103 n=value, 104 startingMonth=1, 105 weekday=1, 106 variation="last", 107 normalize=normalize, 108 ) 109 elif klass is FY5253Quarter: 110 klass = klass( 111 n=value, 112 startingMonth=1, 113 weekday=1, 114 qtr_with_extra_week=1, 115 variation="last", 116 normalize=normalize, 117 ) 118 elif klass is LastWeekOfMonth: 119 klass = klass(n=value, weekday=5, normalize=normalize) 120 elif klass is WeekOfMonth: 121 klass = klass(n=value, week=1, weekday=5, normalize=normalize) 122 elif klass is Week: 123 klass = klass(n=value, weekday=5, normalize=normalize) 124 elif klass is DateOffset: 125 klass = klass(days=value, normalize=normalize) 126 else: 127 klass = klass(value, normalize=normalize) 128 return klass 129 130 def test_apply_out_of_range(self, tz_naive_fixture): 131 tz = tz_naive_fixture 132 if self._offset is None: 133 return 134 if isinstance(tz, tzlocal) and not IS64: 135 pytest.xfail(reason="OverflowError inside tzlocal past 2038") 136 137 # try to create an out-of-bounds result timestamp; if we can't create 138 # the offset skip 139 try: 140 if self._offset in (BusinessHour, CustomBusinessHour): 141 # Using 10000 in BusinessHour fails in tz check because of DST 142 # difference 143 offset = self._get_offset(self._offset, value=100000) 144 else: 145 offset = self._get_offset(self._offset, value=10000) 146 147 result = Timestamp("20080101") + offset 148 assert isinstance(result, datetime) 149 assert result.tzinfo is None 150 151 # Check tz is preserved 152 t = Timestamp("20080101", tz=tz) 153 result = t + offset 154 assert isinstance(result, datetime) 155 assert t.tzinfo == result.tzinfo 156 157 except OutOfBoundsDatetime: 158 pass 159 except (ValueError, KeyError): 160 # we are creating an invalid offset 161 # so ignore 162 pass 163 164 def test_offsets_compare_equal(self): 165 # root cause of GH#456: __ne__ was not implemented 166 if self._offset is None: 167 return 168 offset1 = self._offset() 169 offset2 = self._offset() 170 assert not offset1 != offset2 171 assert offset1 == offset2 172 173 def test_rsub(self): 174 if self._offset is None or not hasattr(self, "offset2"): 175 # i.e. skip for TestCommon and YQM subclasses that do not have 176 # offset2 attr 177 return 178 assert self.d - self.offset2 == (-self.offset2).apply(self.d) 179 180 def test_radd(self): 181 if self._offset is None or not hasattr(self, "offset2"): 182 # i.e. skip for TestCommon and YQM subclasses that do not have 183 # offset2 attr 184 return 185 assert self.d + self.offset2 == self.offset2 + self.d 186 187 def test_sub(self): 188 if self._offset is None or not hasattr(self, "offset2"): 189 # i.e. skip for TestCommon and YQM subclasses that do not have 190 # offset2 attr 191 return 192 off = self.offset2 193 msg = "Cannot subtract datetime from offset" 194 with pytest.raises(TypeError, match=msg): 195 off - self.d 196 197 assert 2 * off - off == off 198 assert self.d - self.offset2 == self.d + self._offset(-2) 199 assert self.d - self.offset2 == self.d - (2 * off - off) 200 201 def testMult1(self): 202 if self._offset is None or not hasattr(self, "offset1"): 203 # i.e. skip for TestCommon and YQM subclasses that do not have 204 # offset1 attr 205 return 206 assert self.d + 10 * self.offset1 == self.d + self._offset(10) 207 assert self.d + 5 * self.offset1 == self.d + self._offset(5) 208 209 def testMult2(self): 210 if self._offset is None: 211 return 212 assert self.d + (-5 * self._offset(-10)) == self.d + self._offset(50) 213 assert self.d + (-3 * self._offset(-2)) == self.d + self._offset(6) 214 215 def test_compare_str(self): 216 # GH#23524 217 # comparing to strings that cannot be cast to DateOffsets should 218 # not raise for __eq__ or __ne__ 219 if self._offset is None: 220 return 221 off = self._get_offset(self._offset) 222 223 assert not off == "infer" 224 assert off != "foo" 225 # Note: inequalities are only implemented for Tick subclasses; 226 # tests for this are in test_ticks 227 228 229class TestCommon(Base): 230 # exected value created by Base._get_offset 231 # are applied to 2011/01/01 09:00 (Saturday) 232 # used for .apply and .rollforward 233 expecteds = { 234 "Day": Timestamp("2011-01-02 09:00:00"), 235 "DateOffset": Timestamp("2011-01-02 09:00:00"), 236 "BusinessDay": Timestamp("2011-01-03 09:00:00"), 237 "CustomBusinessDay": Timestamp("2011-01-03 09:00:00"), 238 "CustomBusinessMonthEnd": Timestamp("2011-01-31 09:00:00"), 239 "CustomBusinessMonthBegin": Timestamp("2011-01-03 09:00:00"), 240 "MonthBegin": Timestamp("2011-02-01 09:00:00"), 241 "BusinessMonthBegin": Timestamp("2011-01-03 09:00:00"), 242 "MonthEnd": Timestamp("2011-01-31 09:00:00"), 243 "SemiMonthEnd": Timestamp("2011-01-15 09:00:00"), 244 "SemiMonthBegin": Timestamp("2011-01-15 09:00:00"), 245 "BusinessMonthEnd": Timestamp("2011-01-31 09:00:00"), 246 "YearBegin": Timestamp("2012-01-01 09:00:00"), 247 "BYearBegin": Timestamp("2011-01-03 09:00:00"), 248 "YearEnd": Timestamp("2011-12-31 09:00:00"), 249 "BYearEnd": Timestamp("2011-12-30 09:00:00"), 250 "QuarterBegin": Timestamp("2011-03-01 09:00:00"), 251 "BQuarterBegin": Timestamp("2011-03-01 09:00:00"), 252 "QuarterEnd": Timestamp("2011-03-31 09:00:00"), 253 "BQuarterEnd": Timestamp("2011-03-31 09:00:00"), 254 "BusinessHour": Timestamp("2011-01-03 10:00:00"), 255 "CustomBusinessHour": Timestamp("2011-01-03 10:00:00"), 256 "WeekOfMonth": Timestamp("2011-01-08 09:00:00"), 257 "LastWeekOfMonth": Timestamp("2011-01-29 09:00:00"), 258 "FY5253Quarter": Timestamp("2011-01-25 09:00:00"), 259 "FY5253": Timestamp("2011-01-25 09:00:00"), 260 "Week": Timestamp("2011-01-08 09:00:00"), 261 "Easter": Timestamp("2011-04-24 09:00:00"), 262 "Hour": Timestamp("2011-01-01 10:00:00"), 263 "Minute": Timestamp("2011-01-01 09:01:00"), 264 "Second": Timestamp("2011-01-01 09:00:01"), 265 "Milli": Timestamp("2011-01-01 09:00:00.001000"), 266 "Micro": Timestamp("2011-01-01 09:00:00.000001"), 267 "Nano": Timestamp(np_datetime64_compat("2011-01-01T09:00:00.000000001Z")), 268 } 269 270 def test_immutable(self, offset_types): 271 # GH#21341 check that __setattr__ raises 272 offset = self._get_offset(offset_types) 273 msg = "objects is not writable|DateOffset objects are immutable" 274 with pytest.raises(AttributeError, match=msg): 275 offset.normalize = True 276 with pytest.raises(AttributeError, match=msg): 277 offset.n = 91 278 279 def test_return_type(self, offset_types): 280 offset = self._get_offset(offset_types) 281 282 # make sure that we are returning a Timestamp 283 result = Timestamp("20080101") + offset 284 assert isinstance(result, Timestamp) 285 286 # make sure that we are returning NaT 287 assert NaT + offset is NaT 288 assert offset + NaT is NaT 289 290 assert NaT - offset is NaT 291 assert (-offset).apply(NaT) is NaT 292 293 def test_offset_n(self, offset_types): 294 offset = self._get_offset(offset_types) 295 assert offset.n == 1 296 297 neg_offset = offset * -1 298 assert neg_offset.n == -1 299 300 mul_offset = offset * 3 301 assert mul_offset.n == 3 302 303 def test_offset_timedelta64_arg(self, offset_types): 304 # check that offset._validate_n raises TypeError on a timedelt64 305 # object 306 off = self._get_offset(offset_types) 307 308 td64 = np.timedelta64(4567, "s") 309 with pytest.raises(TypeError, match="argument must be an integer"): 310 type(off)(n=td64, **off.kwds) 311 312 def test_offset_mul_ndarray(self, offset_types): 313 off = self._get_offset(offset_types) 314 315 expected = np.array([[off, off * 2], [off * 3, off * 4]]) 316 317 result = np.array([[1, 2], [3, 4]]) * off 318 tm.assert_numpy_array_equal(result, expected) 319 320 result = off * np.array([[1, 2], [3, 4]]) 321 tm.assert_numpy_array_equal(result, expected) 322 323 def test_offset_freqstr(self, offset_types): 324 offset = self._get_offset(offset_types) 325 326 freqstr = offset.freqstr 327 if freqstr not in ("<Easter>", "<DateOffset: days=1>", "LWOM-SAT"): 328 code = _get_offset(freqstr) 329 assert offset.rule_code == code 330 331 def _check_offsetfunc_works(self, offset, funcname, dt, expected, normalize=False): 332 333 if normalize and issubclass(offset, Tick): 334 # normalize=True disallowed for Tick subclasses GH#21427 335 return 336 337 offset_s = self._get_offset(offset, normalize=normalize) 338 func = getattr(offset_s, funcname) 339 340 result = func(dt) 341 assert isinstance(result, Timestamp) 342 assert result == expected 343 344 result = func(Timestamp(dt)) 345 assert isinstance(result, Timestamp) 346 assert result == expected 347 348 # see gh-14101 349 exp_warning = None 350 ts = Timestamp(dt) + Nano(5) 351 352 if ( 353 type(offset_s).__name__ == "DateOffset" 354 and (funcname == "apply" or normalize) 355 and ts.nanosecond > 0 356 ): 357 exp_warning = UserWarning 358 359 # test nanosecond is preserved 360 with tm.assert_produces_warning(exp_warning, check_stacklevel=False): 361 result = func(ts) 362 assert isinstance(result, Timestamp) 363 if normalize is False: 364 assert result == expected + Nano(5) 365 else: 366 assert result == expected 367 368 if isinstance(dt, np.datetime64): 369 # test tz when input is datetime or Timestamp 370 return 371 372 for tz in self.timezones: 373 expected_localize = expected.tz_localize(tz) 374 tz_obj = timezones.maybe_get_tz(tz) 375 dt_tz = conversion.localize_pydatetime(dt, tz_obj) 376 377 result = func(dt_tz) 378 assert isinstance(result, Timestamp) 379 assert result == expected_localize 380 381 result = func(Timestamp(dt, tz=tz)) 382 assert isinstance(result, Timestamp) 383 assert result == expected_localize 384 385 # see gh-14101 386 exp_warning = None 387 ts = Timestamp(dt, tz=tz) + Nano(5) 388 389 if ( 390 type(offset_s).__name__ == "DateOffset" 391 and (funcname == "apply" or normalize) 392 and ts.nanosecond > 0 393 ): 394 exp_warning = UserWarning 395 396 # test nanosecond is preserved 397 with tm.assert_produces_warning(exp_warning, check_stacklevel=False): 398 result = func(ts) 399 assert isinstance(result, Timestamp) 400 if normalize is False: 401 assert result == expected_localize + Nano(5) 402 else: 403 assert result == expected_localize 404 405 def test_apply(self, offset_types): 406 sdt = datetime(2011, 1, 1, 9, 0) 407 ndt = np_datetime64_compat("2011-01-01 09:00Z") 408 409 for dt in [sdt, ndt]: 410 expected = self.expecteds[offset_types.__name__] 411 self._check_offsetfunc_works(offset_types, "apply", dt, expected) 412 413 expected = Timestamp(expected.date()) 414 self._check_offsetfunc_works( 415 offset_types, "apply", dt, expected, normalize=True 416 ) 417 418 def test_rollforward(self, offset_types): 419 expecteds = self.expecteds.copy() 420 421 # result will not be changed if the target is on the offset 422 no_changes = [ 423 "Day", 424 "MonthBegin", 425 "SemiMonthBegin", 426 "YearBegin", 427 "Week", 428 "Hour", 429 "Minute", 430 "Second", 431 "Milli", 432 "Micro", 433 "Nano", 434 "DateOffset", 435 ] 436 for n in no_changes: 437 expecteds[n] = Timestamp("2011/01/01 09:00") 438 439 expecteds["BusinessHour"] = Timestamp("2011-01-03 09:00:00") 440 expecteds["CustomBusinessHour"] = Timestamp("2011-01-03 09:00:00") 441 442 # but be changed when normalize=True 443 norm_expected = expecteds.copy() 444 for k in norm_expected: 445 norm_expected[k] = Timestamp(norm_expected[k].date()) 446 447 normalized = { 448 "Day": Timestamp("2011-01-02 00:00:00"), 449 "DateOffset": Timestamp("2011-01-02 00:00:00"), 450 "MonthBegin": Timestamp("2011-02-01 00:00:00"), 451 "SemiMonthBegin": Timestamp("2011-01-15 00:00:00"), 452 "YearBegin": Timestamp("2012-01-01 00:00:00"), 453 "Week": Timestamp("2011-01-08 00:00:00"), 454 "Hour": Timestamp("2011-01-01 00:00:00"), 455 "Minute": Timestamp("2011-01-01 00:00:00"), 456 "Second": Timestamp("2011-01-01 00:00:00"), 457 "Milli": Timestamp("2011-01-01 00:00:00"), 458 "Micro": Timestamp("2011-01-01 00:00:00"), 459 } 460 norm_expected.update(normalized) 461 462 sdt = datetime(2011, 1, 1, 9, 0) 463 ndt = np_datetime64_compat("2011-01-01 09:00Z") 464 465 for dt in [sdt, ndt]: 466 expected = expecteds[offset_types.__name__] 467 self._check_offsetfunc_works(offset_types, "rollforward", dt, expected) 468 expected = norm_expected[offset_types.__name__] 469 self._check_offsetfunc_works( 470 offset_types, "rollforward", dt, expected, normalize=True 471 ) 472 473 def test_rollback(self, offset_types): 474 expecteds = { 475 "BusinessDay": Timestamp("2010-12-31 09:00:00"), 476 "CustomBusinessDay": Timestamp("2010-12-31 09:00:00"), 477 "CustomBusinessMonthEnd": Timestamp("2010-12-31 09:00:00"), 478 "CustomBusinessMonthBegin": Timestamp("2010-12-01 09:00:00"), 479 "BusinessMonthBegin": Timestamp("2010-12-01 09:00:00"), 480 "MonthEnd": Timestamp("2010-12-31 09:00:00"), 481 "SemiMonthEnd": Timestamp("2010-12-31 09:00:00"), 482 "BusinessMonthEnd": Timestamp("2010-12-31 09:00:00"), 483 "BYearBegin": Timestamp("2010-01-01 09:00:00"), 484 "YearEnd": Timestamp("2010-12-31 09:00:00"), 485 "BYearEnd": Timestamp("2010-12-31 09:00:00"), 486 "QuarterBegin": Timestamp("2010-12-01 09:00:00"), 487 "BQuarterBegin": Timestamp("2010-12-01 09:00:00"), 488 "QuarterEnd": Timestamp("2010-12-31 09:00:00"), 489 "BQuarterEnd": Timestamp("2010-12-31 09:00:00"), 490 "BusinessHour": Timestamp("2010-12-31 17:00:00"), 491 "CustomBusinessHour": Timestamp("2010-12-31 17:00:00"), 492 "WeekOfMonth": Timestamp("2010-12-11 09:00:00"), 493 "LastWeekOfMonth": Timestamp("2010-12-25 09:00:00"), 494 "FY5253Quarter": Timestamp("2010-10-26 09:00:00"), 495 "FY5253": Timestamp("2010-01-26 09:00:00"), 496 "Easter": Timestamp("2010-04-04 09:00:00"), 497 } 498 499 # result will not be changed if the target is on the offset 500 for n in [ 501 "Day", 502 "MonthBegin", 503 "SemiMonthBegin", 504 "YearBegin", 505 "Week", 506 "Hour", 507 "Minute", 508 "Second", 509 "Milli", 510 "Micro", 511 "Nano", 512 "DateOffset", 513 ]: 514 expecteds[n] = Timestamp("2011/01/01 09:00") 515 516 # but be changed when normalize=True 517 norm_expected = expecteds.copy() 518 for k in norm_expected: 519 norm_expected[k] = Timestamp(norm_expected[k].date()) 520 521 normalized = { 522 "Day": Timestamp("2010-12-31 00:00:00"), 523 "DateOffset": Timestamp("2010-12-31 00:00:00"), 524 "MonthBegin": Timestamp("2010-12-01 00:00:00"), 525 "SemiMonthBegin": Timestamp("2010-12-15 00:00:00"), 526 "YearBegin": Timestamp("2010-01-01 00:00:00"), 527 "Week": Timestamp("2010-12-25 00:00:00"), 528 "Hour": Timestamp("2011-01-01 00:00:00"), 529 "Minute": Timestamp("2011-01-01 00:00:00"), 530 "Second": Timestamp("2011-01-01 00:00:00"), 531 "Milli": Timestamp("2011-01-01 00:00:00"), 532 "Micro": Timestamp("2011-01-01 00:00:00"), 533 } 534 norm_expected.update(normalized) 535 536 sdt = datetime(2011, 1, 1, 9, 0) 537 ndt = np_datetime64_compat("2011-01-01 09:00Z") 538 539 for dt in [sdt, ndt]: 540 expected = expecteds[offset_types.__name__] 541 self._check_offsetfunc_works(offset_types, "rollback", dt, expected) 542 543 expected = norm_expected[offset_types.__name__] 544 self._check_offsetfunc_works( 545 offset_types, "rollback", dt, expected, normalize=True 546 ) 547 548 def test_is_on_offset(self, offset_types): 549 dt = self.expecteds[offset_types.__name__] 550 offset_s = self._get_offset(offset_types) 551 assert offset_s.is_on_offset(dt) 552 553 # when normalize=True, is_on_offset checks time is 00:00:00 554 if issubclass(offset_types, Tick): 555 # normalize=True disallowed for Tick subclasses GH#21427 556 return 557 offset_n = self._get_offset(offset_types, normalize=True) 558 assert not offset_n.is_on_offset(dt) 559 560 if offset_types in (BusinessHour, CustomBusinessHour): 561 # In default BusinessHour (9:00-17:00), normalized time 562 # cannot be in business hour range 563 return 564 date = datetime(dt.year, dt.month, dt.day) 565 assert offset_n.is_on_offset(date) 566 567 def test_add(self, offset_types, tz_naive_fixture): 568 tz = tz_naive_fixture 569 dt = datetime(2011, 1, 1, 9, 0) 570 571 offset_s = self._get_offset(offset_types) 572 expected = self.expecteds[offset_types.__name__] 573 574 result_dt = dt + offset_s 575 result_ts = Timestamp(dt) + offset_s 576 for result in [result_dt, result_ts]: 577 assert isinstance(result, Timestamp) 578 assert result == expected 579 580 expected_localize = expected.tz_localize(tz) 581 result = Timestamp(dt, tz=tz) + offset_s 582 assert isinstance(result, Timestamp) 583 assert result == expected_localize 584 585 # normalize=True, disallowed for Tick subclasses GH#21427 586 if issubclass(offset_types, Tick): 587 return 588 offset_s = self._get_offset(offset_types, normalize=True) 589 expected = Timestamp(expected.date()) 590 591 result_dt = dt + offset_s 592 result_ts = Timestamp(dt) + offset_s 593 for result in [result_dt, result_ts]: 594 assert isinstance(result, Timestamp) 595 assert result == expected 596 597 expected_localize = expected.tz_localize(tz) 598 result = Timestamp(dt, tz=tz) + offset_s 599 assert isinstance(result, Timestamp) 600 assert result == expected_localize 601 602 def test_add_empty_datetimeindex(self, offset_types, tz_naive_fixture): 603 # GH#12724, GH#30336 604 offset_s = self._get_offset(offset_types) 605 606 dti = DatetimeIndex([], tz=tz_naive_fixture) 607 608 warn = None 609 if isinstance( 610 offset_s, 611 ( 612 Easter, 613 WeekOfMonth, 614 LastWeekOfMonth, 615 CustomBusinessDay, 616 BusinessHour, 617 CustomBusinessHour, 618 CustomBusinessMonthBegin, 619 CustomBusinessMonthEnd, 620 FY5253, 621 FY5253Quarter, 622 ), 623 ): 624 # We don't have an optimized apply_index 625 warn = PerformanceWarning 626 627 with tm.assert_produces_warning(warn): 628 result = dti + offset_s 629 tm.assert_index_equal(result, dti) 630 with tm.assert_produces_warning(warn): 631 result = offset_s + dti 632 tm.assert_index_equal(result, dti) 633 634 dta = dti._data 635 with tm.assert_produces_warning(warn): 636 result = dta + offset_s 637 tm.assert_equal(result, dta) 638 with tm.assert_produces_warning(warn): 639 result = offset_s + dta 640 tm.assert_equal(result, dta) 641 642 def test_pickle_roundtrip(self, offset_types): 643 off = self._get_offset(offset_types) 644 res = tm.round_trip_pickle(off) 645 assert off == res 646 if type(off) is not DateOffset: 647 for attr in off._attributes: 648 if attr == "calendar": 649 # np.busdaycalendar __eq__ will return False; 650 # we check holidays and weekmask attrs so are OK 651 continue 652 # Make sure nothings got lost from _params (which __eq__) is based on 653 assert getattr(off, attr) == getattr(res, attr) 654 655 def test_pickle_dateoffset_odd_inputs(self): 656 # GH#34511 657 off = DateOffset(months=12) 658 res = tm.round_trip_pickle(off) 659 assert off == res 660 661 base_dt = datetime(2020, 1, 1) 662 assert base_dt + off == base_dt + res 663 664 def test_onOffset_deprecated(self, offset_types): 665 # GH#30340 use idiomatic naming 666 off = self._get_offset(offset_types) 667 668 ts = Timestamp.now() 669 with tm.assert_produces_warning(FutureWarning): 670 result = off.onOffset(ts) 671 672 expected = off.is_on_offset(ts) 673 assert result == expected 674 675 def test_isAnchored_deprecated(self, offset_types): 676 # GH#30340 use idiomatic naming 677 off = self._get_offset(offset_types) 678 679 with tm.assert_produces_warning(FutureWarning): 680 result = off.isAnchored() 681 682 expected = off.is_anchored() 683 assert result == expected 684 685 def test_offsets_hashable(self, offset_types): 686 # GH: 37267 687 off = self._get_offset(offset_types) 688 assert hash(off) is not None 689 690 691class TestDateOffset(Base): 692 def setup_method(self, method): 693 self.d = Timestamp(datetime(2008, 1, 2)) 694 _offset_map.clear() 695 696 def test_repr(self): 697 repr(DateOffset()) 698 repr(DateOffset(2)) 699 repr(2 * DateOffset()) 700 repr(2 * DateOffset(months=2)) 701 702 def test_mul(self): 703 assert DateOffset(2) == 2 * DateOffset(1) 704 assert DateOffset(2) == DateOffset(1) * 2 705 706 def test_constructor(self): 707 708 assert (self.d + DateOffset(months=2)) == datetime(2008, 3, 2) 709 assert (self.d - DateOffset(months=2)) == datetime(2007, 11, 2) 710 711 assert (self.d + DateOffset(2)) == datetime(2008, 1, 4) 712 713 assert not DateOffset(2).is_anchored() 714 assert DateOffset(1).is_anchored() 715 716 d = datetime(2008, 1, 31) 717 assert (d + DateOffset(months=1)) == datetime(2008, 2, 29) 718 719 def test_copy(self): 720 assert DateOffset(months=2).copy() == DateOffset(months=2) 721 722 def test_eq(self): 723 offset1 = DateOffset(days=1) 724 offset2 = DateOffset(days=365) 725 726 assert offset1 != offset2 727 728 729class TestBusinessDay(Base): 730 _offset = BDay 731 732 def setup_method(self, method): 733 self.d = datetime(2008, 1, 1) 734 735 self.offset = BDay() 736 self.offset1 = self.offset 737 self.offset2 = BDay(2) 738 739 def test_different_normalize_equals(self): 740 # GH#21404 changed __eq__ to return False when `normalize` does not match 741 offset = self._offset() 742 offset2 = self._offset(normalize=True) 743 assert offset != offset2 744 745 def test_repr(self): 746 assert repr(self.offset) == "<BusinessDay>" 747 assert repr(self.offset2) == "<2 * BusinessDays>" 748 749 expected = "<BusinessDay: offset=datetime.timedelta(days=1)>" 750 assert repr(self.offset + timedelta(1)) == expected 751 752 def test_with_offset(self): 753 offset = self.offset + timedelta(hours=2) 754 755 assert (self.d + offset) == datetime(2008, 1, 2, 2) 756 757 def test_with_offset_index(self): 758 dti = DatetimeIndex([self.d]) 759 result = dti + (self.offset + timedelta(hours=2)) 760 761 expected = DatetimeIndex([datetime(2008, 1, 2, 2)]) 762 tm.assert_index_equal(result, expected) 763 764 def test_eq(self): 765 assert self.offset2 == self.offset2 766 767 def test_mul(self): 768 pass 769 770 def test_hash(self): 771 assert hash(self.offset2) == hash(self.offset2) 772 773 def test_call(self): 774 with tm.assert_produces_warning(FutureWarning): 775 # GH#34171 DateOffset.__call__ is deprecated 776 assert self.offset2(self.d) == datetime(2008, 1, 3) 777 778 def testRollback1(self): 779 assert BDay(10).rollback(self.d) == self.d 780 781 def testRollback2(self): 782 assert BDay(10).rollback(datetime(2008, 1, 5)) == datetime(2008, 1, 4) 783 784 def testRollforward1(self): 785 assert BDay(10).rollforward(self.d) == self.d 786 787 def testRollforward2(self): 788 assert BDay(10).rollforward(datetime(2008, 1, 5)) == datetime(2008, 1, 7) 789 790 def test_roll_date_object(self): 791 offset = BDay() 792 793 dt = date(2012, 9, 15) 794 795 result = offset.rollback(dt) 796 assert result == datetime(2012, 9, 14) 797 798 result = offset.rollforward(dt) 799 assert result == datetime(2012, 9, 17) 800 801 offset = offsets.Day() 802 result = offset.rollback(dt) 803 assert result == datetime(2012, 9, 15) 804 805 result = offset.rollforward(dt) 806 assert result == datetime(2012, 9, 15) 807 808 def test_is_on_offset(self): 809 tests = [ 810 (BDay(), datetime(2008, 1, 1), True), 811 (BDay(), datetime(2008, 1, 5), False), 812 ] 813 814 for offset, d, expected in tests: 815 assert_is_on_offset(offset, d, expected) 816 817 apply_cases: _ApplyCases = [] 818 apply_cases.append( 819 ( 820 BDay(), 821 { 822 datetime(2008, 1, 1): datetime(2008, 1, 2), 823 datetime(2008, 1, 4): datetime(2008, 1, 7), 824 datetime(2008, 1, 5): datetime(2008, 1, 7), 825 datetime(2008, 1, 6): datetime(2008, 1, 7), 826 datetime(2008, 1, 7): datetime(2008, 1, 8), 827 }, 828 ) 829 ) 830 831 apply_cases.append( 832 ( 833 2 * BDay(), 834 { 835 datetime(2008, 1, 1): datetime(2008, 1, 3), 836 datetime(2008, 1, 4): datetime(2008, 1, 8), 837 datetime(2008, 1, 5): datetime(2008, 1, 8), 838 datetime(2008, 1, 6): datetime(2008, 1, 8), 839 datetime(2008, 1, 7): datetime(2008, 1, 9), 840 }, 841 ) 842 ) 843 844 apply_cases.append( 845 ( 846 -BDay(), 847 { 848 datetime(2008, 1, 1): datetime(2007, 12, 31), 849 datetime(2008, 1, 4): datetime(2008, 1, 3), 850 datetime(2008, 1, 5): datetime(2008, 1, 4), 851 datetime(2008, 1, 6): datetime(2008, 1, 4), 852 datetime(2008, 1, 7): datetime(2008, 1, 4), 853 datetime(2008, 1, 8): datetime(2008, 1, 7), 854 }, 855 ) 856 ) 857 858 apply_cases.append( 859 ( 860 -2 * BDay(), 861 { 862 datetime(2008, 1, 1): datetime(2007, 12, 28), 863 datetime(2008, 1, 4): datetime(2008, 1, 2), 864 datetime(2008, 1, 5): datetime(2008, 1, 3), 865 datetime(2008, 1, 6): datetime(2008, 1, 3), 866 datetime(2008, 1, 7): datetime(2008, 1, 3), 867 datetime(2008, 1, 8): datetime(2008, 1, 4), 868 datetime(2008, 1, 9): datetime(2008, 1, 7), 869 }, 870 ) 871 ) 872 873 apply_cases.append( 874 ( 875 BDay(0), 876 { 877 datetime(2008, 1, 1): datetime(2008, 1, 1), 878 datetime(2008, 1, 4): datetime(2008, 1, 4), 879 datetime(2008, 1, 5): datetime(2008, 1, 7), 880 datetime(2008, 1, 6): datetime(2008, 1, 7), 881 datetime(2008, 1, 7): datetime(2008, 1, 7), 882 }, 883 ) 884 ) 885 886 @pytest.mark.parametrize("case", apply_cases) 887 def test_apply(self, case): 888 offset, cases = case 889 for base, expected in cases.items(): 890 assert_offset_equal(offset, base, expected) 891 892 def test_apply_large_n(self): 893 dt = datetime(2012, 10, 23) 894 895 result = dt + BDay(10) 896 assert result == datetime(2012, 11, 6) 897 898 result = dt + BDay(100) - BDay(100) 899 assert result == dt 900 901 off = BDay() * 6 902 rs = datetime(2012, 1, 1) - off 903 xp = datetime(2011, 12, 23) 904 assert rs == xp 905 906 st = datetime(2011, 12, 18) 907 rs = st + off 908 xp = datetime(2011, 12, 26) 909 assert rs == xp 910 911 off = BDay() * 10 912 rs = datetime(2014, 1, 5) + off # see #5890 913 xp = datetime(2014, 1, 17) 914 assert rs == xp 915 916 def test_apply_corner(self): 917 msg = "Only know how to combine business day with datetime or timedelta" 918 with pytest.raises(ApplyTypeError, match=msg): 919 BDay().apply(BMonthEnd()) 920 921 922class TestBusinessHour(Base): 923 _offset = BusinessHour 924 925 def setup_method(self, method): 926 self.d = datetime(2014, 7, 1, 10, 00) 927 928 self.offset1 = BusinessHour() 929 self.offset2 = BusinessHour(n=3) 930 931 self.offset3 = BusinessHour(n=-1) 932 self.offset4 = BusinessHour(n=-4) 933 934 from datetime import time as dt_time 935 936 self.offset5 = BusinessHour(start=dt_time(11, 0), end=dt_time(14, 30)) 937 self.offset6 = BusinessHour(start="20:00", end="05:00") 938 self.offset7 = BusinessHour(n=-2, start=dt_time(21, 30), end=dt_time(6, 30)) 939 self.offset8 = BusinessHour(start=["09:00", "13:00"], end=["12:00", "17:00"]) 940 self.offset9 = BusinessHour( 941 n=3, start=["09:00", "22:00"], end=["13:00", "03:00"] 942 ) 943 self.offset10 = BusinessHour( 944 n=-1, start=["23:00", "13:00"], end=["02:00", "17:00"] 945 ) 946 947 @pytest.mark.parametrize( 948 "start,end,match", 949 [ 950 ( 951 dt_time(11, 0, 5), 952 "17:00", 953 "time data must be specified only with hour and minute", 954 ), 955 ("AAA", "17:00", "time data must match '%H:%M' format"), 956 ("14:00:05", "17:00", "time data must match '%H:%M' format"), 957 ([], "17:00", "Must include at least 1 start time"), 958 ("09:00", [], "Must include at least 1 end time"), 959 ( 960 ["09:00", "11:00"], 961 "17:00", 962 "number of starting time and ending time must be the same", 963 ), 964 ( 965 ["09:00", "11:00"], 966 ["10:00"], 967 "number of starting time and ending time must be the same", 968 ), 969 ( 970 ["09:00", "11:00"], 971 ["12:00", "20:00"], 972 r"invalid starting and ending time\(s\): opening hours should not " 973 "touch or overlap with one another", 974 ), 975 ( 976 ["12:00", "20:00"], 977 ["09:00", "11:00"], 978 r"invalid starting and ending time\(s\): opening hours should not " 979 "touch or overlap with one another", 980 ), 981 ], 982 ) 983 def test_constructor_errors(self, start, end, match): 984 with pytest.raises(ValueError, match=match): 985 BusinessHour(start=start, end=end) 986 987 def test_different_normalize_equals(self): 988 # GH#21404 changed __eq__ to return False when `normalize` does not match 989 offset = self._offset() 990 offset2 = self._offset(normalize=True) 991 assert offset != offset2 992 993 def test_repr(self): 994 assert repr(self.offset1) == "<BusinessHour: BH=09:00-17:00>" 995 assert repr(self.offset2) == "<3 * BusinessHours: BH=09:00-17:00>" 996 assert repr(self.offset3) == "<-1 * BusinessHour: BH=09:00-17:00>" 997 assert repr(self.offset4) == "<-4 * BusinessHours: BH=09:00-17:00>" 998 999 assert repr(self.offset5) == "<BusinessHour: BH=11:00-14:30>" 1000 assert repr(self.offset6) == "<BusinessHour: BH=20:00-05:00>" 1001 assert repr(self.offset7) == "<-2 * BusinessHours: BH=21:30-06:30>" 1002 assert repr(self.offset8) == "<BusinessHour: BH=09:00-12:00,13:00-17:00>" 1003 assert repr(self.offset9) == "<3 * BusinessHours: BH=09:00-13:00,22:00-03:00>" 1004 assert repr(self.offset10) == "<-1 * BusinessHour: BH=13:00-17:00,23:00-02:00>" 1005 1006 def test_with_offset(self): 1007 expected = Timestamp("2014-07-01 13:00") 1008 1009 assert self.d + BusinessHour() * 3 == expected 1010 assert self.d + BusinessHour(n=3) == expected 1011 1012 @pytest.mark.parametrize( 1013 "offset_name", 1014 ["offset1", "offset2", "offset3", "offset4", "offset8", "offset9", "offset10"], 1015 ) 1016 def test_eq_attribute(self, offset_name): 1017 offset = getattr(self, offset_name) 1018 assert offset == offset 1019 1020 @pytest.mark.parametrize( 1021 "offset1,offset2", 1022 [ 1023 (BusinessHour(start="09:00"), BusinessHour()), 1024 ( 1025 BusinessHour(start=["23:00", "13:00"], end=["12:00", "17:00"]), 1026 BusinessHour(start=["13:00", "23:00"], end=["17:00", "12:00"]), 1027 ), 1028 ], 1029 ) 1030 def test_eq(self, offset1, offset2): 1031 assert offset1 == offset2 1032 1033 @pytest.mark.parametrize( 1034 "offset1,offset2", 1035 [ 1036 (BusinessHour(), BusinessHour(-1)), 1037 (BusinessHour(start="09:00"), BusinessHour(start="09:01")), 1038 ( 1039 BusinessHour(start="09:00", end="17:00"), 1040 BusinessHour(start="17:00", end="09:01"), 1041 ), 1042 ( 1043 BusinessHour(start=["13:00", "23:00"], end=["18:00", "07:00"]), 1044 BusinessHour(start=["13:00", "23:00"], end=["17:00", "12:00"]), 1045 ), 1046 ], 1047 ) 1048 def test_neq(self, offset1, offset2): 1049 assert offset1 != offset2 1050 1051 @pytest.mark.parametrize( 1052 "offset_name", 1053 ["offset1", "offset2", "offset3", "offset4", "offset8", "offset9", "offset10"], 1054 ) 1055 def test_hash(self, offset_name): 1056 offset = getattr(self, offset_name) 1057 assert offset == offset 1058 1059 def test_call(self): 1060 with tm.assert_produces_warning(FutureWarning): 1061 # GH#34171 DateOffset.__call__ is deprecated 1062 assert self.offset1(self.d) == datetime(2014, 7, 1, 11) 1063 assert self.offset2(self.d) == datetime(2014, 7, 1, 13) 1064 assert self.offset3(self.d) == datetime(2014, 6, 30, 17) 1065 assert self.offset4(self.d) == datetime(2014, 6, 30, 14) 1066 assert self.offset8(self.d) == datetime(2014, 7, 1, 11) 1067 assert self.offset9(self.d) == datetime(2014, 7, 1, 22) 1068 assert self.offset10(self.d) == datetime(2014, 7, 1, 1) 1069 1070 def test_sub(self): 1071 # we have to override test_sub here because self.offset2 is not 1072 # defined as self._offset(2) 1073 off = self.offset2 1074 msg = "Cannot subtract datetime from offset" 1075 with pytest.raises(TypeError, match=msg): 1076 off - self.d 1077 assert 2 * off - off == off 1078 1079 assert self.d - self.offset2 == self.d + self._offset(-3) 1080 1081 def testRollback1(self): 1082 assert self.offset1.rollback(self.d) == self.d 1083 assert self.offset2.rollback(self.d) == self.d 1084 assert self.offset3.rollback(self.d) == self.d 1085 assert self.offset4.rollback(self.d) == self.d 1086 assert self.offset5.rollback(self.d) == datetime(2014, 6, 30, 14, 30) 1087 assert self.offset6.rollback(self.d) == datetime(2014, 7, 1, 5, 0) 1088 assert self.offset7.rollback(self.d) == datetime(2014, 7, 1, 6, 30) 1089 assert self.offset8.rollback(self.d) == self.d 1090 assert self.offset9.rollback(self.d) == self.d 1091 assert self.offset10.rollback(self.d) == datetime(2014, 7, 1, 2) 1092 1093 d = datetime(2014, 7, 1, 0) 1094 assert self.offset1.rollback(d) == datetime(2014, 6, 30, 17) 1095 assert self.offset2.rollback(d) == datetime(2014, 6, 30, 17) 1096 assert self.offset3.rollback(d) == datetime(2014, 6, 30, 17) 1097 assert self.offset4.rollback(d) == datetime(2014, 6, 30, 17) 1098 assert self.offset5.rollback(d) == datetime(2014, 6, 30, 14, 30) 1099 assert self.offset6.rollback(d) == d 1100 assert self.offset7.rollback(d) == d 1101 assert self.offset8.rollback(d) == datetime(2014, 6, 30, 17) 1102 assert self.offset9.rollback(d) == d 1103 assert self.offset10.rollback(d) == d 1104 1105 assert self._offset(5).rollback(self.d) == self.d 1106 1107 def testRollback2(self): 1108 assert self._offset(-3).rollback(datetime(2014, 7, 5, 15, 0)) == datetime( 1109 2014, 7, 4, 17, 0 1110 ) 1111 1112 def testRollforward1(self): 1113 assert self.offset1.rollforward(self.d) == self.d 1114 assert self.offset2.rollforward(self.d) == self.d 1115 assert self.offset3.rollforward(self.d) == self.d 1116 assert self.offset4.rollforward(self.d) == self.d 1117 assert self.offset5.rollforward(self.d) == datetime(2014, 7, 1, 11, 0) 1118 assert self.offset6.rollforward(self.d) == datetime(2014, 7, 1, 20, 0) 1119 assert self.offset7.rollforward(self.d) == datetime(2014, 7, 1, 21, 30) 1120 assert self.offset8.rollforward(self.d) == self.d 1121 assert self.offset9.rollforward(self.d) == self.d 1122 assert self.offset10.rollforward(self.d) == datetime(2014, 7, 1, 13) 1123 1124 d = datetime(2014, 7, 1, 0) 1125 assert self.offset1.rollforward(d) == datetime(2014, 7, 1, 9) 1126 assert self.offset2.rollforward(d) == datetime(2014, 7, 1, 9) 1127 assert self.offset3.rollforward(d) == datetime(2014, 7, 1, 9) 1128 assert self.offset4.rollforward(d) == datetime(2014, 7, 1, 9) 1129 assert self.offset5.rollforward(d) == datetime(2014, 7, 1, 11) 1130 assert self.offset6.rollforward(d) == d 1131 assert self.offset7.rollforward(d) == d 1132 assert self.offset8.rollforward(d) == datetime(2014, 7, 1, 9) 1133 assert self.offset9.rollforward(d) == d 1134 assert self.offset10.rollforward(d) == d 1135 1136 assert self._offset(5).rollforward(self.d) == self.d 1137 1138 def testRollforward2(self): 1139 assert self._offset(-3).rollforward(datetime(2014, 7, 5, 16, 0)) == datetime( 1140 2014, 7, 7, 9 1141 ) 1142 1143 def test_roll_date_object(self): 1144 offset = BusinessHour() 1145 1146 dt = datetime(2014, 7, 6, 15, 0) 1147 1148 result = offset.rollback(dt) 1149 assert result == datetime(2014, 7, 4, 17) 1150 1151 result = offset.rollforward(dt) 1152 assert result == datetime(2014, 7, 7, 9) 1153 1154 normalize_cases = [] 1155 normalize_cases.append( 1156 ( 1157 BusinessHour(normalize=True), 1158 { 1159 datetime(2014, 7, 1, 8): datetime(2014, 7, 1), 1160 datetime(2014, 7, 1, 17): datetime(2014, 7, 2), 1161 datetime(2014, 7, 1, 16): datetime(2014, 7, 2), 1162 datetime(2014, 7, 1, 23): datetime(2014, 7, 2), 1163 datetime(2014, 7, 1, 0): datetime(2014, 7, 1), 1164 datetime(2014, 7, 4, 15): datetime(2014, 7, 4), 1165 datetime(2014, 7, 4, 15, 59): datetime(2014, 7, 4), 1166 datetime(2014, 7, 4, 16, 30): datetime(2014, 7, 7), 1167 datetime(2014, 7, 5, 23): datetime(2014, 7, 7), 1168 datetime(2014, 7, 6, 10): datetime(2014, 7, 7), 1169 }, 1170 ) 1171 ) 1172 1173 normalize_cases.append( 1174 ( 1175 BusinessHour(-1, normalize=True), 1176 { 1177 datetime(2014, 7, 1, 8): datetime(2014, 6, 30), 1178 datetime(2014, 7, 1, 17): datetime(2014, 7, 1), 1179 datetime(2014, 7, 1, 16): datetime(2014, 7, 1), 1180 datetime(2014, 7, 1, 10): datetime(2014, 6, 30), 1181 datetime(2014, 7, 1, 0): datetime(2014, 6, 30), 1182 datetime(2014, 7, 7, 10): datetime(2014, 7, 4), 1183 datetime(2014, 7, 7, 10, 1): datetime(2014, 7, 7), 1184 datetime(2014, 7, 5, 23): datetime(2014, 7, 4), 1185 datetime(2014, 7, 6, 10): datetime(2014, 7, 4), 1186 }, 1187 ) 1188 ) 1189 1190 normalize_cases.append( 1191 ( 1192 BusinessHour(1, normalize=True, start="17:00", end="04:00"), 1193 { 1194 datetime(2014, 7, 1, 8): datetime(2014, 7, 1), 1195 datetime(2014, 7, 1, 17): datetime(2014, 7, 1), 1196 datetime(2014, 7, 1, 23): datetime(2014, 7, 2), 1197 datetime(2014, 7, 2, 2): datetime(2014, 7, 2), 1198 datetime(2014, 7, 2, 3): datetime(2014, 7, 2), 1199 datetime(2014, 7, 4, 23): datetime(2014, 7, 5), 1200 datetime(2014, 7, 5, 2): datetime(2014, 7, 5), 1201 datetime(2014, 7, 7, 2): datetime(2014, 7, 7), 1202 datetime(2014, 7, 7, 17): datetime(2014, 7, 7), 1203 }, 1204 ) 1205 ) 1206 1207 @pytest.mark.parametrize("case", normalize_cases) 1208 def test_normalize(self, case): 1209 offset, cases = case 1210 for dt, expected in cases.items(): 1211 assert offset.apply(dt) == expected 1212 1213 on_offset_cases = [] 1214 on_offset_cases.append( 1215 ( 1216 BusinessHour(), 1217 { 1218 datetime(2014, 7, 1, 9): True, 1219 datetime(2014, 7, 1, 8, 59): False, 1220 datetime(2014, 7, 1, 8): False, 1221 datetime(2014, 7, 1, 17): True, 1222 datetime(2014, 7, 1, 17, 1): False, 1223 datetime(2014, 7, 1, 18): False, 1224 datetime(2014, 7, 5, 9): False, 1225 datetime(2014, 7, 6, 12): False, 1226 }, 1227 ) 1228 ) 1229 1230 on_offset_cases.append( 1231 ( 1232 BusinessHour(start="10:00", end="15:00"), 1233 { 1234 datetime(2014, 7, 1, 9): False, 1235 datetime(2014, 7, 1, 10): True, 1236 datetime(2014, 7, 1, 15): True, 1237 datetime(2014, 7, 1, 15, 1): False, 1238 datetime(2014, 7, 5, 12): False, 1239 datetime(2014, 7, 6, 12): False, 1240 }, 1241 ) 1242 ) 1243 1244 on_offset_cases.append( 1245 ( 1246 BusinessHour(start="19:00", end="05:00"), 1247 { 1248 datetime(2014, 7, 1, 9, 0): False, 1249 datetime(2014, 7, 1, 10, 0): False, 1250 datetime(2014, 7, 1, 15): False, 1251 datetime(2014, 7, 1, 15, 1): False, 1252 datetime(2014, 7, 5, 12, 0): False, 1253 datetime(2014, 7, 6, 12, 0): False, 1254 datetime(2014, 7, 1, 19, 0): True, 1255 datetime(2014, 7, 2, 0, 0): True, 1256 datetime(2014, 7, 4, 23): True, 1257 datetime(2014, 7, 5, 1): True, 1258 datetime(2014, 7, 5, 5, 0): True, 1259 datetime(2014, 7, 6, 23, 0): False, 1260 datetime(2014, 7, 7, 3, 0): False, 1261 }, 1262 ) 1263 ) 1264 1265 on_offset_cases.append( 1266 ( 1267 BusinessHour(start=["09:00", "13:00"], end=["12:00", "17:00"]), 1268 { 1269 datetime(2014, 7, 1, 9): True, 1270 datetime(2014, 7, 1, 8, 59): False, 1271 datetime(2014, 7, 1, 8): False, 1272 datetime(2014, 7, 1, 17): True, 1273 datetime(2014, 7, 1, 17, 1): False, 1274 datetime(2014, 7, 1, 18): False, 1275 datetime(2014, 7, 5, 9): False, 1276 datetime(2014, 7, 6, 12): False, 1277 datetime(2014, 7, 1, 12, 30): False, 1278 }, 1279 ) 1280 ) 1281 1282 on_offset_cases.append( 1283 ( 1284 BusinessHour(start=["19:00", "23:00"], end=["21:00", "05:00"]), 1285 { 1286 datetime(2014, 7, 1, 9, 0): False, 1287 datetime(2014, 7, 1, 10, 0): False, 1288 datetime(2014, 7, 1, 15): False, 1289 datetime(2014, 7, 1, 15, 1): False, 1290 datetime(2014, 7, 5, 12, 0): False, 1291 datetime(2014, 7, 6, 12, 0): False, 1292 datetime(2014, 7, 1, 19, 0): True, 1293 datetime(2014, 7, 2, 0, 0): True, 1294 datetime(2014, 7, 4, 23): True, 1295 datetime(2014, 7, 5, 1): True, 1296 datetime(2014, 7, 5, 5, 0): True, 1297 datetime(2014, 7, 6, 23, 0): False, 1298 datetime(2014, 7, 7, 3, 0): False, 1299 datetime(2014, 7, 4, 22): False, 1300 }, 1301 ) 1302 ) 1303 1304 @pytest.mark.parametrize("case", on_offset_cases) 1305 def test_is_on_offset(self, case): 1306 offset, cases = case 1307 for dt, expected in cases.items(): 1308 assert offset.is_on_offset(dt) == expected 1309 1310 opening_time_cases = [] 1311 # opening time should be affected by sign of n, not by n's value and 1312 # end 1313 opening_time_cases.append( 1314 ( 1315 [ 1316 BusinessHour(), 1317 BusinessHour(n=2), 1318 BusinessHour(n=4), 1319 BusinessHour(end="10:00"), 1320 BusinessHour(n=2, end="4:00"), 1321 BusinessHour(n=4, end="15:00"), 1322 ], 1323 { 1324 datetime(2014, 7, 1, 11): ( 1325 datetime(2014, 7, 2, 9), 1326 datetime(2014, 7, 1, 9), 1327 ), 1328 datetime(2014, 7, 1, 18): ( 1329 datetime(2014, 7, 2, 9), 1330 datetime(2014, 7, 1, 9), 1331 ), 1332 datetime(2014, 7, 1, 23): ( 1333 datetime(2014, 7, 2, 9), 1334 datetime(2014, 7, 1, 9), 1335 ), 1336 datetime(2014, 7, 2, 8): ( 1337 datetime(2014, 7, 2, 9), 1338 datetime(2014, 7, 1, 9), 1339 ), 1340 # if timestamp is on opening time, next opening time is 1341 # as it is 1342 datetime(2014, 7, 2, 9): ( 1343 datetime(2014, 7, 2, 9), 1344 datetime(2014, 7, 2, 9), 1345 ), 1346 datetime(2014, 7, 2, 10): ( 1347 datetime(2014, 7, 3, 9), 1348 datetime(2014, 7, 2, 9), 1349 ), 1350 # 2014-07-05 is saturday 1351 datetime(2014, 7, 5, 10): ( 1352 datetime(2014, 7, 7, 9), 1353 datetime(2014, 7, 4, 9), 1354 ), 1355 datetime(2014, 7, 4, 10): ( 1356 datetime(2014, 7, 7, 9), 1357 datetime(2014, 7, 4, 9), 1358 ), 1359 datetime(2014, 7, 4, 23): ( 1360 datetime(2014, 7, 7, 9), 1361 datetime(2014, 7, 4, 9), 1362 ), 1363 datetime(2014, 7, 6, 10): ( 1364 datetime(2014, 7, 7, 9), 1365 datetime(2014, 7, 4, 9), 1366 ), 1367 datetime(2014, 7, 7, 5): ( 1368 datetime(2014, 7, 7, 9), 1369 datetime(2014, 7, 4, 9), 1370 ), 1371 datetime(2014, 7, 7, 9, 1): ( 1372 datetime(2014, 7, 8, 9), 1373 datetime(2014, 7, 7, 9), 1374 ), 1375 }, 1376 ) 1377 ) 1378 1379 opening_time_cases.append( 1380 ( 1381 [ 1382 BusinessHour(start="11:15"), 1383 BusinessHour(n=2, start="11:15"), 1384 BusinessHour(n=3, start="11:15"), 1385 BusinessHour(start="11:15", end="10:00"), 1386 BusinessHour(n=2, start="11:15", end="4:00"), 1387 BusinessHour(n=3, start="11:15", end="15:00"), 1388 ], 1389 { 1390 datetime(2014, 7, 1, 11): ( 1391 datetime(2014, 7, 1, 11, 15), 1392 datetime(2014, 6, 30, 11, 15), 1393 ), 1394 datetime(2014, 7, 1, 18): ( 1395 datetime(2014, 7, 2, 11, 15), 1396 datetime(2014, 7, 1, 11, 15), 1397 ), 1398 datetime(2014, 7, 1, 23): ( 1399 datetime(2014, 7, 2, 11, 15), 1400 datetime(2014, 7, 1, 11, 15), 1401 ), 1402 datetime(2014, 7, 2, 8): ( 1403 datetime(2014, 7, 2, 11, 15), 1404 datetime(2014, 7, 1, 11, 15), 1405 ), 1406 datetime(2014, 7, 2, 9): ( 1407 datetime(2014, 7, 2, 11, 15), 1408 datetime(2014, 7, 1, 11, 15), 1409 ), 1410 datetime(2014, 7, 2, 10): ( 1411 datetime(2014, 7, 2, 11, 15), 1412 datetime(2014, 7, 1, 11, 15), 1413 ), 1414 datetime(2014, 7, 2, 11, 15): ( 1415 datetime(2014, 7, 2, 11, 15), 1416 datetime(2014, 7, 2, 11, 15), 1417 ), 1418 datetime(2014, 7, 2, 11, 15, 1): ( 1419 datetime(2014, 7, 3, 11, 15), 1420 datetime(2014, 7, 2, 11, 15), 1421 ), 1422 datetime(2014, 7, 5, 10): ( 1423 datetime(2014, 7, 7, 11, 15), 1424 datetime(2014, 7, 4, 11, 15), 1425 ), 1426 datetime(2014, 7, 4, 10): ( 1427 datetime(2014, 7, 4, 11, 15), 1428 datetime(2014, 7, 3, 11, 15), 1429 ), 1430 datetime(2014, 7, 4, 23): ( 1431 datetime(2014, 7, 7, 11, 15), 1432 datetime(2014, 7, 4, 11, 15), 1433 ), 1434 datetime(2014, 7, 6, 10): ( 1435 datetime(2014, 7, 7, 11, 15), 1436 datetime(2014, 7, 4, 11, 15), 1437 ), 1438 datetime(2014, 7, 7, 5): ( 1439 datetime(2014, 7, 7, 11, 15), 1440 datetime(2014, 7, 4, 11, 15), 1441 ), 1442 datetime(2014, 7, 7, 9, 1): ( 1443 datetime(2014, 7, 7, 11, 15), 1444 datetime(2014, 7, 4, 11, 15), 1445 ), 1446 }, 1447 ) 1448 ) 1449 1450 opening_time_cases.append( 1451 ( 1452 [ 1453 BusinessHour(-1), 1454 BusinessHour(n=-2), 1455 BusinessHour(n=-4), 1456 BusinessHour(n=-1, end="10:00"), 1457 BusinessHour(n=-2, end="4:00"), 1458 BusinessHour(n=-4, end="15:00"), 1459 ], 1460 { 1461 datetime(2014, 7, 1, 11): ( 1462 datetime(2014, 7, 1, 9), 1463 datetime(2014, 7, 2, 9), 1464 ), 1465 datetime(2014, 7, 1, 18): ( 1466 datetime(2014, 7, 1, 9), 1467 datetime(2014, 7, 2, 9), 1468 ), 1469 datetime(2014, 7, 1, 23): ( 1470 datetime(2014, 7, 1, 9), 1471 datetime(2014, 7, 2, 9), 1472 ), 1473 datetime(2014, 7, 2, 8): ( 1474 datetime(2014, 7, 1, 9), 1475 datetime(2014, 7, 2, 9), 1476 ), 1477 datetime(2014, 7, 2, 9): ( 1478 datetime(2014, 7, 2, 9), 1479 datetime(2014, 7, 2, 9), 1480 ), 1481 datetime(2014, 7, 2, 10): ( 1482 datetime(2014, 7, 2, 9), 1483 datetime(2014, 7, 3, 9), 1484 ), 1485 datetime(2014, 7, 5, 10): ( 1486 datetime(2014, 7, 4, 9), 1487 datetime(2014, 7, 7, 9), 1488 ), 1489 datetime(2014, 7, 4, 10): ( 1490 datetime(2014, 7, 4, 9), 1491 datetime(2014, 7, 7, 9), 1492 ), 1493 datetime(2014, 7, 4, 23): ( 1494 datetime(2014, 7, 4, 9), 1495 datetime(2014, 7, 7, 9), 1496 ), 1497 datetime(2014, 7, 6, 10): ( 1498 datetime(2014, 7, 4, 9), 1499 datetime(2014, 7, 7, 9), 1500 ), 1501 datetime(2014, 7, 7, 5): ( 1502 datetime(2014, 7, 4, 9), 1503 datetime(2014, 7, 7, 9), 1504 ), 1505 datetime(2014, 7, 7, 9): ( 1506 datetime(2014, 7, 7, 9), 1507 datetime(2014, 7, 7, 9), 1508 ), 1509 datetime(2014, 7, 7, 9, 1): ( 1510 datetime(2014, 7, 7, 9), 1511 datetime(2014, 7, 8, 9), 1512 ), 1513 }, 1514 ) 1515 ) 1516 1517 opening_time_cases.append( 1518 ( 1519 [ 1520 BusinessHour(start="17:00", end="05:00"), 1521 BusinessHour(n=3, start="17:00", end="03:00"), 1522 ], 1523 { 1524 datetime(2014, 7, 1, 11): ( 1525 datetime(2014, 7, 1, 17), 1526 datetime(2014, 6, 30, 17), 1527 ), 1528 datetime(2014, 7, 1, 18): ( 1529 datetime(2014, 7, 2, 17), 1530 datetime(2014, 7, 1, 17), 1531 ), 1532 datetime(2014, 7, 1, 23): ( 1533 datetime(2014, 7, 2, 17), 1534 datetime(2014, 7, 1, 17), 1535 ), 1536 datetime(2014, 7, 2, 8): ( 1537 datetime(2014, 7, 2, 17), 1538 datetime(2014, 7, 1, 17), 1539 ), 1540 datetime(2014, 7, 2, 9): ( 1541 datetime(2014, 7, 2, 17), 1542 datetime(2014, 7, 1, 17), 1543 ), 1544 datetime(2014, 7, 4, 17): ( 1545 datetime(2014, 7, 4, 17), 1546 datetime(2014, 7, 4, 17), 1547 ), 1548 datetime(2014, 7, 5, 10): ( 1549 datetime(2014, 7, 7, 17), 1550 datetime(2014, 7, 4, 17), 1551 ), 1552 datetime(2014, 7, 4, 10): ( 1553 datetime(2014, 7, 4, 17), 1554 datetime(2014, 7, 3, 17), 1555 ), 1556 datetime(2014, 7, 4, 23): ( 1557 datetime(2014, 7, 7, 17), 1558 datetime(2014, 7, 4, 17), 1559 ), 1560 datetime(2014, 7, 6, 10): ( 1561 datetime(2014, 7, 7, 17), 1562 datetime(2014, 7, 4, 17), 1563 ), 1564 datetime(2014, 7, 7, 5): ( 1565 datetime(2014, 7, 7, 17), 1566 datetime(2014, 7, 4, 17), 1567 ), 1568 datetime(2014, 7, 7, 17, 1): ( 1569 datetime(2014, 7, 8, 17), 1570 datetime(2014, 7, 7, 17), 1571 ), 1572 }, 1573 ) 1574 ) 1575 1576 opening_time_cases.append( 1577 ( 1578 [ 1579 BusinessHour(-1, start="17:00", end="05:00"), 1580 BusinessHour(n=-2, start="17:00", end="03:00"), 1581 ], 1582 { 1583 datetime(2014, 7, 1, 11): ( 1584 datetime(2014, 6, 30, 17), 1585 datetime(2014, 7, 1, 17), 1586 ), 1587 datetime(2014, 7, 1, 18): ( 1588 datetime(2014, 7, 1, 17), 1589 datetime(2014, 7, 2, 17), 1590 ), 1591 datetime(2014, 7, 1, 23): ( 1592 datetime(2014, 7, 1, 17), 1593 datetime(2014, 7, 2, 17), 1594 ), 1595 datetime(2014, 7, 2, 8): ( 1596 datetime(2014, 7, 1, 17), 1597 datetime(2014, 7, 2, 17), 1598 ), 1599 datetime(2014, 7, 2, 9): ( 1600 datetime(2014, 7, 1, 17), 1601 datetime(2014, 7, 2, 17), 1602 ), 1603 datetime(2014, 7, 2, 16, 59): ( 1604 datetime(2014, 7, 1, 17), 1605 datetime(2014, 7, 2, 17), 1606 ), 1607 datetime(2014, 7, 5, 10): ( 1608 datetime(2014, 7, 4, 17), 1609 datetime(2014, 7, 7, 17), 1610 ), 1611 datetime(2014, 7, 4, 10): ( 1612 datetime(2014, 7, 3, 17), 1613 datetime(2014, 7, 4, 17), 1614 ), 1615 datetime(2014, 7, 4, 23): ( 1616 datetime(2014, 7, 4, 17), 1617 datetime(2014, 7, 7, 17), 1618 ), 1619 datetime(2014, 7, 6, 10): ( 1620 datetime(2014, 7, 4, 17), 1621 datetime(2014, 7, 7, 17), 1622 ), 1623 datetime(2014, 7, 7, 5): ( 1624 datetime(2014, 7, 4, 17), 1625 datetime(2014, 7, 7, 17), 1626 ), 1627 datetime(2014, 7, 7, 18): ( 1628 datetime(2014, 7, 7, 17), 1629 datetime(2014, 7, 8, 17), 1630 ), 1631 }, 1632 ) 1633 ) 1634 1635 opening_time_cases.append( 1636 ( 1637 [ 1638 BusinessHour(start=["11:15", "15:00"], end=["13:00", "20:00"]), 1639 BusinessHour(n=3, start=["11:15", "15:00"], end=["12:00", "20:00"]), 1640 BusinessHour(start=["11:15", "15:00"], end=["13:00", "17:00"]), 1641 BusinessHour(n=2, start=["11:15", "15:00"], end=["12:00", "03:00"]), 1642 BusinessHour(n=3, start=["11:15", "15:00"], end=["13:00", "16:00"]), 1643 ], 1644 { 1645 datetime(2014, 7, 1, 11): ( 1646 datetime(2014, 7, 1, 11, 15), 1647 datetime(2014, 6, 30, 15), 1648 ), 1649 datetime(2014, 7, 1, 18): ( 1650 datetime(2014, 7, 2, 11, 15), 1651 datetime(2014, 7, 1, 15), 1652 ), 1653 datetime(2014, 7, 1, 23): ( 1654 datetime(2014, 7, 2, 11, 15), 1655 datetime(2014, 7, 1, 15), 1656 ), 1657 datetime(2014, 7, 2, 8): ( 1658 datetime(2014, 7, 2, 11, 15), 1659 datetime(2014, 7, 1, 15), 1660 ), 1661 datetime(2014, 7, 2, 9): ( 1662 datetime(2014, 7, 2, 11, 15), 1663 datetime(2014, 7, 1, 15), 1664 ), 1665 datetime(2014, 7, 2, 10): ( 1666 datetime(2014, 7, 2, 11, 15), 1667 datetime(2014, 7, 1, 15), 1668 ), 1669 datetime(2014, 7, 2, 11, 15): ( 1670 datetime(2014, 7, 2, 11, 15), 1671 datetime(2014, 7, 2, 11, 15), 1672 ), 1673 datetime(2014, 7, 2, 11, 15, 1): ( 1674 datetime(2014, 7, 2, 15), 1675 datetime(2014, 7, 2, 11, 15), 1676 ), 1677 datetime(2014, 7, 5, 10): ( 1678 datetime(2014, 7, 7, 11, 15), 1679 datetime(2014, 7, 4, 15), 1680 ), 1681 datetime(2014, 7, 4, 10): ( 1682 datetime(2014, 7, 4, 11, 15), 1683 datetime(2014, 7, 3, 15), 1684 ), 1685 datetime(2014, 7, 4, 23): ( 1686 datetime(2014, 7, 7, 11, 15), 1687 datetime(2014, 7, 4, 15), 1688 ), 1689 datetime(2014, 7, 6, 10): ( 1690 datetime(2014, 7, 7, 11, 15), 1691 datetime(2014, 7, 4, 15), 1692 ), 1693 datetime(2014, 7, 7, 5): ( 1694 datetime(2014, 7, 7, 11, 15), 1695 datetime(2014, 7, 4, 15), 1696 ), 1697 datetime(2014, 7, 7, 9, 1): ( 1698 datetime(2014, 7, 7, 11, 15), 1699 datetime(2014, 7, 4, 15), 1700 ), 1701 datetime(2014, 7, 7, 12): ( 1702 datetime(2014, 7, 7, 15), 1703 datetime(2014, 7, 7, 11, 15), 1704 ), 1705 }, 1706 ) 1707 ) 1708 1709 opening_time_cases.append( 1710 ( 1711 [ 1712 BusinessHour(n=-1, start=["17:00", "08:00"], end=["05:00", "10:00"]), 1713 BusinessHour(n=-2, start=["08:00", "17:00"], end=["10:00", "03:00"]), 1714 ], 1715 { 1716 datetime(2014, 7, 1, 11): ( 1717 datetime(2014, 7, 1, 8), 1718 datetime(2014, 7, 1, 17), 1719 ), 1720 datetime(2014, 7, 1, 18): ( 1721 datetime(2014, 7, 1, 17), 1722 datetime(2014, 7, 2, 8), 1723 ), 1724 datetime(2014, 7, 1, 23): ( 1725 datetime(2014, 7, 1, 17), 1726 datetime(2014, 7, 2, 8), 1727 ), 1728 datetime(2014, 7, 2, 8): ( 1729 datetime(2014, 7, 2, 8), 1730 datetime(2014, 7, 2, 8), 1731 ), 1732 datetime(2014, 7, 2, 9): ( 1733 datetime(2014, 7, 2, 8), 1734 datetime(2014, 7, 2, 17), 1735 ), 1736 datetime(2014, 7, 2, 16, 59): ( 1737 datetime(2014, 7, 2, 8), 1738 datetime(2014, 7, 2, 17), 1739 ), 1740 datetime(2014, 7, 5, 10): ( 1741 datetime(2014, 7, 4, 17), 1742 datetime(2014, 7, 7, 8), 1743 ), 1744 datetime(2014, 7, 4, 10): ( 1745 datetime(2014, 7, 4, 8), 1746 datetime(2014, 7, 4, 17), 1747 ), 1748 datetime(2014, 7, 4, 23): ( 1749 datetime(2014, 7, 4, 17), 1750 datetime(2014, 7, 7, 8), 1751 ), 1752 datetime(2014, 7, 6, 10): ( 1753 datetime(2014, 7, 4, 17), 1754 datetime(2014, 7, 7, 8), 1755 ), 1756 datetime(2014, 7, 7, 5): ( 1757 datetime(2014, 7, 4, 17), 1758 datetime(2014, 7, 7, 8), 1759 ), 1760 datetime(2014, 7, 7, 18): ( 1761 datetime(2014, 7, 7, 17), 1762 datetime(2014, 7, 8, 8), 1763 ), 1764 }, 1765 ) 1766 ) 1767 1768 @pytest.mark.parametrize("case", opening_time_cases) 1769 def test_opening_time(self, case): 1770 _offsets, cases = case 1771 for offset in _offsets: 1772 for dt, (exp_next, exp_prev) in cases.items(): 1773 assert offset._next_opening_time(dt) == exp_next 1774 assert offset._prev_opening_time(dt) == exp_prev 1775 1776 apply_cases = [] 1777 apply_cases.append( 1778 ( 1779 BusinessHour(), 1780 { 1781 datetime(2014, 7, 1, 11): datetime(2014, 7, 1, 12), 1782 datetime(2014, 7, 1, 13): datetime(2014, 7, 1, 14), 1783 datetime(2014, 7, 1, 15): datetime(2014, 7, 1, 16), 1784 datetime(2014, 7, 1, 19): datetime(2014, 7, 2, 10), 1785 datetime(2014, 7, 1, 16): datetime(2014, 7, 2, 9), 1786 datetime(2014, 7, 1, 16, 30, 15): datetime(2014, 7, 2, 9, 30, 15), 1787 datetime(2014, 7, 1, 17): datetime(2014, 7, 2, 10), 1788 datetime(2014, 7, 2, 11): datetime(2014, 7, 2, 12), 1789 # out of business hours 1790 datetime(2014, 7, 2, 8): datetime(2014, 7, 2, 10), 1791 datetime(2014, 7, 2, 19): datetime(2014, 7, 3, 10), 1792 datetime(2014, 7, 2, 23): datetime(2014, 7, 3, 10), 1793 datetime(2014, 7, 3, 0): datetime(2014, 7, 3, 10), 1794 # saturday 1795 datetime(2014, 7, 5, 15): datetime(2014, 7, 7, 10), 1796 datetime(2014, 7, 4, 17): datetime(2014, 7, 7, 10), 1797 datetime(2014, 7, 4, 16, 30): datetime(2014, 7, 7, 9, 30), 1798 datetime(2014, 7, 4, 16, 30, 30): datetime(2014, 7, 7, 9, 30, 30), 1799 }, 1800 ) 1801 ) 1802 1803 apply_cases.append( 1804 ( 1805 BusinessHour(4), 1806 { 1807 datetime(2014, 7, 1, 11): datetime(2014, 7, 1, 15), 1808 datetime(2014, 7, 1, 13): datetime(2014, 7, 2, 9), 1809 datetime(2014, 7, 1, 15): datetime(2014, 7, 2, 11), 1810 datetime(2014, 7, 1, 16): datetime(2014, 7, 2, 12), 1811 datetime(2014, 7, 1, 17): datetime(2014, 7, 2, 13), 1812 datetime(2014, 7, 2, 11): datetime(2014, 7, 2, 15), 1813 datetime(2014, 7, 2, 8): datetime(2014, 7, 2, 13), 1814 datetime(2014, 7, 2, 19): datetime(2014, 7, 3, 13), 1815 datetime(2014, 7, 2, 23): datetime(2014, 7, 3, 13), 1816 datetime(2014, 7, 3, 0): datetime(2014, 7, 3, 13), 1817 datetime(2014, 7, 5, 15): datetime(2014, 7, 7, 13), 1818 datetime(2014, 7, 4, 17): datetime(2014, 7, 7, 13), 1819 datetime(2014, 7, 4, 16, 30): datetime(2014, 7, 7, 12, 30), 1820 datetime(2014, 7, 4, 16, 30, 30): datetime(2014, 7, 7, 12, 30, 30), 1821 }, 1822 ) 1823 ) 1824 1825 apply_cases.append( 1826 ( 1827 BusinessHour(-1), 1828 { 1829 datetime(2014, 7, 1, 11): datetime(2014, 7, 1, 10), 1830 datetime(2014, 7, 1, 13): datetime(2014, 7, 1, 12), 1831 datetime(2014, 7, 1, 15): datetime(2014, 7, 1, 14), 1832 datetime(2014, 7, 1, 16): datetime(2014, 7, 1, 15), 1833 datetime(2014, 7, 1, 10): datetime(2014, 6, 30, 17), 1834 datetime(2014, 7, 1, 16, 30, 15): datetime(2014, 7, 1, 15, 30, 15), 1835 datetime(2014, 7, 1, 9, 30, 15): datetime(2014, 6, 30, 16, 30, 15), 1836 datetime(2014, 7, 1, 17): datetime(2014, 7, 1, 16), 1837 datetime(2014, 7, 1, 5): datetime(2014, 6, 30, 16), 1838 datetime(2014, 7, 2, 11): datetime(2014, 7, 2, 10), 1839 # out of business hours 1840 datetime(2014, 7, 2, 8): datetime(2014, 7, 1, 16), 1841 datetime(2014, 7, 2, 19): datetime(2014, 7, 2, 16), 1842 datetime(2014, 7, 2, 23): datetime(2014, 7, 2, 16), 1843 datetime(2014, 7, 3, 0): datetime(2014, 7, 2, 16), 1844 # saturday 1845 datetime(2014, 7, 5, 15): datetime(2014, 7, 4, 16), 1846 datetime(2014, 7, 7, 9): datetime(2014, 7, 4, 16), 1847 datetime(2014, 7, 7, 9, 30): datetime(2014, 7, 4, 16, 30), 1848 datetime(2014, 7, 7, 9, 30, 30): datetime(2014, 7, 4, 16, 30, 30), 1849 }, 1850 ) 1851 ) 1852 1853 apply_cases.append( 1854 ( 1855 BusinessHour(-4), 1856 { 1857 datetime(2014, 7, 1, 11): datetime(2014, 6, 30, 15), 1858 datetime(2014, 7, 1, 13): datetime(2014, 6, 30, 17), 1859 datetime(2014, 7, 1, 15): datetime(2014, 7, 1, 11), 1860 datetime(2014, 7, 1, 16): datetime(2014, 7, 1, 12), 1861 datetime(2014, 7, 1, 17): datetime(2014, 7, 1, 13), 1862 datetime(2014, 7, 2, 11): datetime(2014, 7, 1, 15), 1863 datetime(2014, 7, 2, 8): datetime(2014, 7, 1, 13), 1864 datetime(2014, 7, 2, 19): datetime(2014, 7, 2, 13), 1865 datetime(2014, 7, 2, 23): datetime(2014, 7, 2, 13), 1866 datetime(2014, 7, 3, 0): datetime(2014, 7, 2, 13), 1867 datetime(2014, 7, 5, 15): datetime(2014, 7, 4, 13), 1868 datetime(2014, 7, 4, 18): datetime(2014, 7, 4, 13), 1869 datetime(2014, 7, 7, 9, 30): datetime(2014, 7, 4, 13, 30), 1870 datetime(2014, 7, 7, 9, 30, 30): datetime(2014, 7, 4, 13, 30, 30), 1871 }, 1872 ) 1873 ) 1874 1875 apply_cases.append( 1876 ( 1877 BusinessHour(start="13:00", end="16:00"), 1878 { 1879 datetime(2014, 7, 1, 11): datetime(2014, 7, 1, 14), 1880 datetime(2014, 7, 1, 13): datetime(2014, 7, 1, 14), 1881 datetime(2014, 7, 1, 15): datetime(2014, 7, 2, 13), 1882 datetime(2014, 7, 1, 19): datetime(2014, 7, 2, 14), 1883 datetime(2014, 7, 1, 16): datetime(2014, 7, 2, 14), 1884 datetime(2014, 7, 1, 15, 30, 15): datetime(2014, 7, 2, 13, 30, 15), 1885 datetime(2014, 7, 5, 15): datetime(2014, 7, 7, 14), 1886 datetime(2014, 7, 4, 17): datetime(2014, 7, 7, 14), 1887 }, 1888 ) 1889 ) 1890 1891 apply_cases.append( 1892 ( 1893 BusinessHour(n=2, start="13:00", end="16:00"), 1894 { 1895 datetime(2014, 7, 1, 17): datetime(2014, 7, 2, 15), 1896 datetime(2014, 7, 2, 14): datetime(2014, 7, 3, 13), 1897 datetime(2014, 7, 2, 8): datetime(2014, 7, 2, 15), 1898 datetime(2014, 7, 2, 19): datetime(2014, 7, 3, 15), 1899 datetime(2014, 7, 2, 14, 30): datetime(2014, 7, 3, 13, 30), 1900 datetime(2014, 7, 3, 0): datetime(2014, 7, 3, 15), 1901 datetime(2014, 7, 5, 15): datetime(2014, 7, 7, 15), 1902 datetime(2014, 7, 4, 17): datetime(2014, 7, 7, 15), 1903 datetime(2014, 7, 4, 14, 30): datetime(2014, 7, 7, 13, 30), 1904 datetime(2014, 7, 4, 14, 30, 30): datetime(2014, 7, 7, 13, 30, 30), 1905 }, 1906 ) 1907 ) 1908 1909 apply_cases.append( 1910 ( 1911 BusinessHour(n=-1, start="13:00", end="16:00"), 1912 { 1913 datetime(2014, 7, 2, 11): datetime(2014, 7, 1, 15), 1914 datetime(2014, 7, 2, 13): datetime(2014, 7, 1, 15), 1915 datetime(2014, 7, 2, 14): datetime(2014, 7, 1, 16), 1916 datetime(2014, 7, 2, 15): datetime(2014, 7, 2, 14), 1917 datetime(2014, 7, 2, 19): datetime(2014, 7, 2, 15), 1918 datetime(2014, 7, 2, 16): datetime(2014, 7, 2, 15), 1919 datetime(2014, 7, 2, 13, 30, 15): datetime(2014, 7, 1, 15, 30, 15), 1920 datetime(2014, 7, 5, 15): datetime(2014, 7, 4, 15), 1921 datetime(2014, 7, 7, 11): datetime(2014, 7, 4, 15), 1922 }, 1923 ) 1924 ) 1925 1926 apply_cases.append( 1927 ( 1928 BusinessHour(n=-3, start="10:00", end="16:00"), 1929 { 1930 datetime(2014, 7, 1, 17): datetime(2014, 7, 1, 13), 1931 datetime(2014, 7, 2, 14): datetime(2014, 7, 2, 11), 1932 datetime(2014, 7, 2, 8): datetime(2014, 7, 1, 13), 1933 datetime(2014, 7, 2, 13): datetime(2014, 7, 1, 16), 1934 datetime(2014, 7, 2, 19): datetime(2014, 7, 2, 13), 1935 datetime(2014, 7, 2, 11, 30): datetime(2014, 7, 1, 14, 30), 1936 datetime(2014, 7, 3, 0): datetime(2014, 7, 2, 13), 1937 datetime(2014, 7, 4, 10): datetime(2014, 7, 3, 13), 1938 datetime(2014, 7, 5, 15): datetime(2014, 7, 4, 13), 1939 datetime(2014, 7, 4, 16): datetime(2014, 7, 4, 13), 1940 datetime(2014, 7, 4, 12, 30): datetime(2014, 7, 3, 15, 30), 1941 datetime(2014, 7, 4, 12, 30, 30): datetime(2014, 7, 3, 15, 30, 30), 1942 }, 1943 ) 1944 ) 1945 1946 apply_cases.append( 1947 ( 1948 BusinessHour(start="19:00", end="05:00"), 1949 { 1950 datetime(2014, 7, 1, 17): datetime(2014, 7, 1, 20), 1951 datetime(2014, 7, 2, 14): datetime(2014, 7, 2, 20), 1952 datetime(2014, 7, 2, 8): datetime(2014, 7, 2, 20), 1953 datetime(2014, 7, 2, 13): datetime(2014, 7, 2, 20), 1954 datetime(2014, 7, 2, 19): datetime(2014, 7, 2, 20), 1955 datetime(2014, 7, 2, 4, 30): datetime(2014, 7, 2, 19, 30), 1956 datetime(2014, 7, 3, 0): datetime(2014, 7, 3, 1), 1957 datetime(2014, 7, 4, 10): datetime(2014, 7, 4, 20), 1958 datetime(2014, 7, 4, 23): datetime(2014, 7, 5, 0), 1959 datetime(2014, 7, 5, 0): datetime(2014, 7, 5, 1), 1960 datetime(2014, 7, 5, 4): datetime(2014, 7, 7, 19), 1961 datetime(2014, 7, 5, 4, 30): datetime(2014, 7, 7, 19, 30), 1962 datetime(2014, 7, 5, 4, 30, 30): datetime(2014, 7, 7, 19, 30, 30), 1963 }, 1964 ) 1965 ) 1966 1967 apply_cases.append( 1968 ( 1969 BusinessHour(n=-1, start="19:00", end="05:00"), 1970 { 1971 datetime(2014, 7, 1, 17): datetime(2014, 7, 1, 4), 1972 datetime(2014, 7, 2, 14): datetime(2014, 7, 2, 4), 1973 datetime(2014, 7, 2, 8): datetime(2014, 7, 2, 4), 1974 datetime(2014, 7, 2, 13): datetime(2014, 7, 2, 4), 1975 datetime(2014, 7, 2, 20): datetime(2014, 7, 2, 5), 1976 datetime(2014, 7, 2, 19): datetime(2014, 7, 2, 4), 1977 datetime(2014, 7, 2, 19, 30): datetime(2014, 7, 2, 4, 30), 1978 datetime(2014, 7, 3, 0): datetime(2014, 7, 2, 23), 1979 datetime(2014, 7, 3, 6): datetime(2014, 7, 3, 4), 1980 datetime(2014, 7, 4, 23): datetime(2014, 7, 4, 22), 1981 datetime(2014, 7, 5, 0): datetime(2014, 7, 4, 23), 1982 datetime(2014, 7, 5, 4): datetime(2014, 7, 5, 3), 1983 datetime(2014, 7, 7, 19, 30): datetime(2014, 7, 5, 4, 30), 1984 datetime(2014, 7, 7, 19, 30, 30): datetime(2014, 7, 5, 4, 30, 30), 1985 }, 1986 ) 1987 ) 1988 1989 # long business hours (see gh-26381) 1990 apply_cases.append( 1991 ( 1992 BusinessHour(n=4, start="00:00", end="23:00"), 1993 { 1994 datetime(2014, 7, 3, 22): datetime(2014, 7, 4, 3), 1995 datetime(2014, 7, 4, 22): datetime(2014, 7, 7, 3), 1996 datetime(2014, 7, 3, 22, 30): datetime(2014, 7, 4, 3, 30), 1997 datetime(2014, 7, 3, 22, 20): datetime(2014, 7, 4, 3, 20), 1998 datetime(2014, 7, 4, 22, 30, 30): datetime(2014, 7, 7, 3, 30, 30), 1999 datetime(2014, 7, 4, 22, 30, 20): datetime(2014, 7, 7, 3, 30, 20), 2000 }, 2001 ) 2002 ) 2003 2004 apply_cases.append( 2005 ( 2006 BusinessHour(n=-4, start="00:00", end="23:00"), 2007 { 2008 datetime(2014, 7, 4, 3): datetime(2014, 7, 3, 22), 2009 datetime(2014, 7, 7, 3): datetime(2014, 7, 4, 22), 2010 datetime(2014, 7, 4, 3, 30): datetime(2014, 7, 3, 22, 30), 2011 datetime(2014, 7, 4, 3, 20): datetime(2014, 7, 3, 22, 20), 2012 datetime(2014, 7, 7, 3, 30, 30): datetime(2014, 7, 4, 22, 30, 30), 2013 datetime(2014, 7, 7, 3, 30, 20): datetime(2014, 7, 4, 22, 30, 20), 2014 }, 2015 ) 2016 ) 2017 2018 # multiple business hours 2019 apply_cases.append( 2020 ( 2021 BusinessHour(start=["09:00", "14:00"], end=["12:00", "18:00"]), 2022 { 2023 datetime(2014, 7, 1, 11): datetime(2014, 7, 1, 14), 2024 datetime(2014, 7, 1, 15): datetime(2014, 7, 1, 16), 2025 datetime(2014, 7, 1, 19): datetime(2014, 7, 2, 10), 2026 datetime(2014, 7, 1, 16): datetime(2014, 7, 1, 17), 2027 datetime(2014, 7, 1, 16, 30, 15): datetime(2014, 7, 1, 17, 30, 15), 2028 datetime(2014, 7, 1, 17): datetime(2014, 7, 2, 9), 2029 datetime(2014, 7, 2, 11): datetime(2014, 7, 2, 14), 2030 # out of business hours 2031 datetime(2014, 7, 1, 13): datetime(2014, 7, 1, 15), 2032 datetime(2014, 7, 2, 8): datetime(2014, 7, 2, 10), 2033 datetime(2014, 7, 2, 19): datetime(2014, 7, 3, 10), 2034 datetime(2014, 7, 2, 23): datetime(2014, 7, 3, 10), 2035 datetime(2014, 7, 3, 0): datetime(2014, 7, 3, 10), 2036 # saturday 2037 datetime(2014, 7, 5, 15): datetime(2014, 7, 7, 10), 2038 datetime(2014, 7, 4, 17): datetime(2014, 7, 7, 9), 2039 datetime(2014, 7, 4, 17, 30): datetime(2014, 7, 7, 9, 30), 2040 datetime(2014, 7, 4, 17, 30, 30): datetime(2014, 7, 7, 9, 30, 30), 2041 }, 2042 ) 2043 ) 2044 2045 apply_cases.append( 2046 ( 2047 BusinessHour(n=4, start=["09:00", "14:00"], end=["12:00", "18:00"]), 2048 { 2049 datetime(2014, 7, 1, 11): datetime(2014, 7, 1, 17), 2050 datetime(2014, 7, 1, 13): datetime(2014, 7, 2, 9), 2051 datetime(2014, 7, 1, 15): datetime(2014, 7, 2, 10), 2052 datetime(2014, 7, 1, 16): datetime(2014, 7, 2, 11), 2053 datetime(2014, 7, 1, 17): datetime(2014, 7, 2, 14), 2054 datetime(2014, 7, 2, 11): datetime(2014, 7, 2, 17), 2055 datetime(2014, 7, 2, 8): datetime(2014, 7, 2, 15), 2056 datetime(2014, 7, 2, 19): datetime(2014, 7, 3, 15), 2057 datetime(2014, 7, 2, 23): datetime(2014, 7, 3, 15), 2058 datetime(2014, 7, 3, 0): datetime(2014, 7, 3, 15), 2059 datetime(2014, 7, 5, 15): datetime(2014, 7, 7, 15), 2060 datetime(2014, 7, 4, 17): datetime(2014, 7, 7, 14), 2061 datetime(2014, 7, 4, 16, 30): datetime(2014, 7, 7, 11, 30), 2062 datetime(2014, 7, 4, 16, 30, 30): datetime(2014, 7, 7, 11, 30, 30), 2063 }, 2064 ) 2065 ) 2066 2067 apply_cases.append( 2068 ( 2069 BusinessHour(n=-4, start=["09:00", "14:00"], end=["12:00", "18:00"]), 2070 { 2071 datetime(2014, 7, 1, 11): datetime(2014, 6, 30, 16), 2072 datetime(2014, 7, 1, 13): datetime(2014, 6, 30, 17), 2073 datetime(2014, 7, 1, 15): datetime(2014, 6, 30, 18), 2074 datetime(2014, 7, 1, 16): datetime(2014, 7, 1, 10), 2075 datetime(2014, 7, 1, 17): datetime(2014, 7, 1, 11), 2076 datetime(2014, 7, 2, 11): datetime(2014, 7, 1, 16), 2077 datetime(2014, 7, 2, 8): datetime(2014, 7, 1, 12), 2078 datetime(2014, 7, 2, 19): datetime(2014, 7, 2, 12), 2079 datetime(2014, 7, 2, 23): datetime(2014, 7, 2, 12), 2080 datetime(2014, 7, 3, 0): datetime(2014, 7, 2, 12), 2081 datetime(2014, 7, 5, 15): datetime(2014, 7, 4, 12), 2082 datetime(2014, 7, 4, 18): datetime(2014, 7, 4, 12), 2083 datetime(2014, 7, 7, 9, 30): datetime(2014, 7, 4, 14, 30), 2084 datetime(2014, 7, 7, 9, 30, 30): datetime(2014, 7, 4, 14, 30, 30), 2085 }, 2086 ) 2087 ) 2088 2089 apply_cases.append( 2090 ( 2091 BusinessHour(n=-1, start=["19:00", "03:00"], end=["01:00", "05:00"]), 2092 { 2093 datetime(2014, 7, 1, 17): datetime(2014, 7, 1, 4), 2094 datetime(2014, 7, 2, 14): datetime(2014, 7, 2, 4), 2095 datetime(2014, 7, 2, 8): datetime(2014, 7, 2, 4), 2096 datetime(2014, 7, 2, 13): datetime(2014, 7, 2, 4), 2097 datetime(2014, 7, 2, 20): datetime(2014, 7, 2, 5), 2098 datetime(2014, 7, 2, 19): datetime(2014, 7, 2, 4), 2099 datetime(2014, 7, 2, 4): datetime(2014, 7, 2, 1), 2100 datetime(2014, 7, 2, 19, 30): datetime(2014, 7, 2, 4, 30), 2101 datetime(2014, 7, 3, 0): datetime(2014, 7, 2, 23), 2102 datetime(2014, 7, 3, 6): datetime(2014, 7, 3, 4), 2103 datetime(2014, 7, 4, 23): datetime(2014, 7, 4, 22), 2104 datetime(2014, 7, 5, 0): datetime(2014, 7, 4, 23), 2105 datetime(2014, 7, 5, 4): datetime(2014, 7, 5, 0), 2106 datetime(2014, 7, 7, 3, 30): datetime(2014, 7, 5, 0, 30), 2107 datetime(2014, 7, 7, 19, 30): datetime(2014, 7, 7, 4, 30), 2108 datetime(2014, 7, 7, 19, 30, 30): datetime(2014, 7, 7, 4, 30, 30), 2109 }, 2110 ) 2111 ) 2112 2113 @pytest.mark.parametrize("case", apply_cases) 2114 def test_apply(self, case): 2115 offset, cases = case 2116 for base, expected in cases.items(): 2117 assert_offset_equal(offset, base, expected) 2118 2119 apply_large_n_cases = [] 2120 # A week later 2121 apply_large_n_cases.append( 2122 ( 2123 BusinessHour(40), 2124 { 2125 datetime(2014, 7, 1, 11): datetime(2014, 7, 8, 11), 2126 datetime(2014, 7, 1, 13): datetime(2014, 7, 8, 13), 2127 datetime(2014, 7, 1, 15): datetime(2014, 7, 8, 15), 2128 datetime(2014, 7, 1, 16): datetime(2014, 7, 8, 16), 2129 datetime(2014, 7, 1, 17): datetime(2014, 7, 9, 9), 2130 datetime(2014, 7, 2, 11): datetime(2014, 7, 9, 11), 2131 datetime(2014, 7, 2, 8): datetime(2014, 7, 9, 9), 2132 datetime(2014, 7, 2, 19): datetime(2014, 7, 10, 9), 2133 datetime(2014, 7, 2, 23): datetime(2014, 7, 10, 9), 2134 datetime(2014, 7, 3, 0): datetime(2014, 7, 10, 9), 2135 datetime(2014, 7, 5, 15): datetime(2014, 7, 14, 9), 2136 datetime(2014, 7, 4, 18): datetime(2014, 7, 14, 9), 2137 datetime(2014, 7, 7, 9, 30): datetime(2014, 7, 14, 9, 30), 2138 datetime(2014, 7, 7, 9, 30, 30): datetime(2014, 7, 14, 9, 30, 30), 2139 }, 2140 ) 2141 ) 2142 2143 # 3 days and 1 hour before 2144 apply_large_n_cases.append( 2145 ( 2146 BusinessHour(-25), 2147 { 2148 datetime(2014, 7, 1, 11): datetime(2014, 6, 26, 10), 2149 datetime(2014, 7, 1, 13): datetime(2014, 6, 26, 12), 2150 datetime(2014, 7, 1, 9): datetime(2014, 6, 25, 16), 2151 datetime(2014, 7, 1, 10): datetime(2014, 6, 25, 17), 2152 datetime(2014, 7, 3, 11): datetime(2014, 6, 30, 10), 2153 datetime(2014, 7, 3, 8): datetime(2014, 6, 27, 16), 2154 datetime(2014, 7, 3, 19): datetime(2014, 6, 30, 16), 2155 datetime(2014, 7, 3, 23): datetime(2014, 6, 30, 16), 2156 datetime(2014, 7, 4, 9): datetime(2014, 6, 30, 16), 2157 datetime(2014, 7, 5, 15): datetime(2014, 7, 1, 16), 2158 datetime(2014, 7, 6, 18): datetime(2014, 7, 1, 16), 2159 datetime(2014, 7, 7, 9, 30): datetime(2014, 7, 1, 16, 30), 2160 datetime(2014, 7, 7, 10, 30, 30): datetime(2014, 7, 2, 9, 30, 30), 2161 }, 2162 ) 2163 ) 2164 2165 # 5 days and 3 hours later 2166 apply_large_n_cases.append( 2167 ( 2168 BusinessHour(28, start="21:00", end="02:00"), 2169 { 2170 datetime(2014, 7, 1, 11): datetime(2014, 7, 9, 0), 2171 datetime(2014, 7, 1, 22): datetime(2014, 7, 9, 1), 2172 datetime(2014, 7, 1, 23): datetime(2014, 7, 9, 21), 2173 datetime(2014, 7, 2, 2): datetime(2014, 7, 10, 0), 2174 datetime(2014, 7, 3, 21): datetime(2014, 7, 11, 0), 2175 datetime(2014, 7, 4, 1): datetime(2014, 7, 11, 23), 2176 datetime(2014, 7, 4, 2): datetime(2014, 7, 12, 0), 2177 datetime(2014, 7, 4, 3): datetime(2014, 7, 12, 0), 2178 datetime(2014, 7, 5, 1): datetime(2014, 7, 14, 23), 2179 datetime(2014, 7, 5, 15): datetime(2014, 7, 15, 0), 2180 datetime(2014, 7, 6, 18): datetime(2014, 7, 15, 0), 2181 datetime(2014, 7, 7, 1): datetime(2014, 7, 15, 0), 2182 datetime(2014, 7, 7, 23, 30): datetime(2014, 7, 15, 21, 30), 2183 }, 2184 ) 2185 ) 2186 2187 # large n for multiple opening hours (3 days and 1 hour before) 2188 apply_large_n_cases.append( 2189 ( 2190 BusinessHour(n=-25, start=["09:00", "14:00"], end=["12:00", "19:00"]), 2191 { 2192 datetime(2014, 7, 1, 11): datetime(2014, 6, 26, 10), 2193 datetime(2014, 7, 1, 13): datetime(2014, 6, 26, 11), 2194 datetime(2014, 7, 1, 9): datetime(2014, 6, 25, 18), 2195 datetime(2014, 7, 1, 10): datetime(2014, 6, 25, 19), 2196 datetime(2014, 7, 3, 11): datetime(2014, 6, 30, 10), 2197 datetime(2014, 7, 3, 8): datetime(2014, 6, 27, 18), 2198 datetime(2014, 7, 3, 19): datetime(2014, 6, 30, 18), 2199 datetime(2014, 7, 3, 23): datetime(2014, 6, 30, 18), 2200 datetime(2014, 7, 4, 9): datetime(2014, 6, 30, 18), 2201 datetime(2014, 7, 5, 15): datetime(2014, 7, 1, 18), 2202 datetime(2014, 7, 6, 18): datetime(2014, 7, 1, 18), 2203 datetime(2014, 7, 7, 9, 30): datetime(2014, 7, 1, 18, 30), 2204 datetime(2014, 7, 7, 10, 30, 30): datetime(2014, 7, 2, 9, 30, 30), 2205 }, 2206 ) 2207 ) 2208 2209 # 5 days and 3 hours later 2210 apply_large_n_cases.append( 2211 ( 2212 BusinessHour(28, start=["21:00", "03:00"], end=["01:00", "04:00"]), 2213 { 2214 datetime(2014, 7, 1, 11): datetime(2014, 7, 9, 0), 2215 datetime(2014, 7, 1, 22): datetime(2014, 7, 9, 3), 2216 datetime(2014, 7, 1, 23): datetime(2014, 7, 9, 21), 2217 datetime(2014, 7, 2, 2): datetime(2014, 7, 9, 23), 2218 datetime(2014, 7, 3, 21): datetime(2014, 7, 11, 0), 2219 datetime(2014, 7, 4, 1): datetime(2014, 7, 11, 23), 2220 datetime(2014, 7, 4, 2): datetime(2014, 7, 11, 23), 2221 datetime(2014, 7, 4, 3): datetime(2014, 7, 11, 23), 2222 datetime(2014, 7, 4, 21): datetime(2014, 7, 12, 0), 2223 datetime(2014, 7, 5, 0): datetime(2014, 7, 14, 22), 2224 datetime(2014, 7, 5, 1): datetime(2014, 7, 14, 23), 2225 datetime(2014, 7, 5, 15): datetime(2014, 7, 14, 23), 2226 datetime(2014, 7, 6, 18): datetime(2014, 7, 14, 23), 2227 datetime(2014, 7, 7, 1): datetime(2014, 7, 14, 23), 2228 datetime(2014, 7, 7, 23, 30): datetime(2014, 7, 15, 21, 30), 2229 }, 2230 ) 2231 ) 2232 2233 @pytest.mark.parametrize("case", apply_large_n_cases) 2234 def test_apply_large_n(self, case): 2235 offset, cases = case 2236 for base, expected in cases.items(): 2237 assert_offset_equal(offset, base, expected) 2238 2239 def test_apply_nanoseconds(self): 2240 tests = [] 2241 2242 tests.append( 2243 ( 2244 BusinessHour(), 2245 { 2246 Timestamp("2014-07-04 15:00") 2247 + Nano(5): Timestamp("2014-07-04 16:00") 2248 + Nano(5), 2249 Timestamp("2014-07-04 16:00") 2250 + Nano(5): Timestamp("2014-07-07 09:00") 2251 + Nano(5), 2252 Timestamp("2014-07-04 16:00") 2253 - Nano(5): Timestamp("2014-07-04 17:00") 2254 - Nano(5), 2255 }, 2256 ) 2257 ) 2258 2259 tests.append( 2260 ( 2261 BusinessHour(-1), 2262 { 2263 Timestamp("2014-07-04 15:00") 2264 + Nano(5): Timestamp("2014-07-04 14:00") 2265 + Nano(5), 2266 Timestamp("2014-07-04 10:00") 2267 + Nano(5): Timestamp("2014-07-04 09:00") 2268 + Nano(5), 2269 Timestamp("2014-07-04 10:00") 2270 - Nano(5): Timestamp("2014-07-03 17:00") 2271 - Nano(5), 2272 }, 2273 ) 2274 ) 2275 2276 for offset, cases in tests: 2277 for base, expected in cases.items(): 2278 assert_offset_equal(offset, base, expected) 2279 2280 def test_datetimeindex(self): 2281 idx1 = date_range(start="2014-07-04 15:00", end="2014-07-08 10:00", freq="BH") 2282 idx2 = date_range(start="2014-07-04 15:00", periods=12, freq="BH") 2283 idx3 = date_range(end="2014-07-08 10:00", periods=12, freq="BH") 2284 expected = DatetimeIndex( 2285 [ 2286 "2014-07-04 15:00", 2287 "2014-07-04 16:00", 2288 "2014-07-07 09:00", 2289 "2014-07-07 10:00", 2290 "2014-07-07 11:00", 2291 "2014-07-07 12:00", 2292 "2014-07-07 13:00", 2293 "2014-07-07 14:00", 2294 "2014-07-07 15:00", 2295 "2014-07-07 16:00", 2296 "2014-07-08 09:00", 2297 "2014-07-08 10:00", 2298 ], 2299 freq="BH", 2300 ) 2301 for idx in [idx1, idx2, idx3]: 2302 tm.assert_index_equal(idx, expected) 2303 2304 idx1 = date_range(start="2014-07-04 15:45", end="2014-07-08 10:45", freq="BH") 2305 idx2 = date_range(start="2014-07-04 15:45", periods=12, freq="BH") 2306 idx3 = date_range(end="2014-07-08 10:45", periods=12, freq="BH") 2307 2308 expected = DatetimeIndex( 2309 [ 2310 "2014-07-04 15:45", 2311 "2014-07-04 16:45", 2312 "2014-07-07 09:45", 2313 "2014-07-07 10:45", 2314 "2014-07-07 11:45", 2315 "2014-07-07 12:45", 2316 "2014-07-07 13:45", 2317 "2014-07-07 14:45", 2318 "2014-07-07 15:45", 2319 "2014-07-07 16:45", 2320 "2014-07-08 09:45", 2321 "2014-07-08 10:45", 2322 ], 2323 freq="BH", 2324 ) 2325 expected = idx1 2326 for idx in [idx1, idx2, idx3]: 2327 tm.assert_index_equal(idx, expected) 2328 2329 2330class TestCustomBusinessHour(Base): 2331 _offset = CustomBusinessHour 2332 holidays = ["2014-06-27", datetime(2014, 6, 30), np.datetime64("2014-07-02")] 2333 2334 def setup_method(self, method): 2335 # 2014 Calendar to check custom holidays 2336 # Sun Mon Tue Wed Thu Fri Sat 2337 # 6/22 23 24 25 26 27 28 2338 # 29 30 7/1 2 3 4 5 2339 # 6 7 8 9 10 11 12 2340 self.d = datetime(2014, 7, 1, 10, 00) 2341 self.offset1 = CustomBusinessHour(weekmask="Tue Wed Thu Fri") 2342 2343 self.offset2 = CustomBusinessHour(holidays=self.holidays) 2344 2345 def test_constructor_errors(self): 2346 from datetime import time as dt_time 2347 2348 msg = "time data must be specified only with hour and minute" 2349 with pytest.raises(ValueError, match=msg): 2350 CustomBusinessHour(start=dt_time(11, 0, 5)) 2351 msg = "time data must match '%H:%M' format" 2352 with pytest.raises(ValueError, match=msg): 2353 CustomBusinessHour(start="AAA") 2354 msg = "time data must match '%H:%M' format" 2355 with pytest.raises(ValueError, match=msg): 2356 CustomBusinessHour(start="14:00:05") 2357 2358 def test_different_normalize_equals(self): 2359 # GH#21404 changed __eq__ to return False when `normalize` does not match 2360 offset = self._offset() 2361 offset2 = self._offset(normalize=True) 2362 assert offset != offset2 2363 2364 def test_repr(self): 2365 assert repr(self.offset1) == "<CustomBusinessHour: CBH=09:00-17:00>" 2366 assert repr(self.offset2) == "<CustomBusinessHour: CBH=09:00-17:00>" 2367 2368 def test_with_offset(self): 2369 expected = Timestamp("2014-07-01 13:00") 2370 2371 assert self.d + CustomBusinessHour() * 3 == expected 2372 assert self.d + CustomBusinessHour(n=3) == expected 2373 2374 def test_eq(self): 2375 for offset in [self.offset1, self.offset2]: 2376 assert offset == offset 2377 2378 assert CustomBusinessHour() != CustomBusinessHour(-1) 2379 assert CustomBusinessHour(start="09:00") == CustomBusinessHour() 2380 assert CustomBusinessHour(start="09:00") != CustomBusinessHour(start="09:01") 2381 assert CustomBusinessHour(start="09:00", end="17:00") != CustomBusinessHour( 2382 start="17:00", end="09:01" 2383 ) 2384 2385 assert CustomBusinessHour(weekmask="Tue Wed Thu Fri") != CustomBusinessHour( 2386 weekmask="Mon Tue Wed Thu Fri" 2387 ) 2388 assert CustomBusinessHour(holidays=["2014-06-27"]) != CustomBusinessHour( 2389 holidays=["2014-06-28"] 2390 ) 2391 2392 def test_sub(self): 2393 # override the Base.test_sub implementation because self.offset2 is 2394 # defined differently in this class than the test expects 2395 pass 2396 2397 def test_hash(self): 2398 assert hash(self.offset1) == hash(self.offset1) 2399 assert hash(self.offset2) == hash(self.offset2) 2400 2401 def test_call(self): 2402 with tm.assert_produces_warning(FutureWarning): 2403 # GH#34171 DateOffset.__call__ is deprecated 2404 assert self.offset1(self.d) == datetime(2014, 7, 1, 11) 2405 assert self.offset2(self.d) == datetime(2014, 7, 1, 11) 2406 2407 def testRollback1(self): 2408 assert self.offset1.rollback(self.d) == self.d 2409 assert self.offset2.rollback(self.d) == self.d 2410 2411 d = datetime(2014, 7, 1, 0) 2412 2413 # 2014/07/01 is Tuesday, 06/30 is Monday(holiday) 2414 assert self.offset1.rollback(d) == datetime(2014, 6, 27, 17) 2415 2416 # 2014/6/30 and 2014/6/27 are holidays 2417 assert self.offset2.rollback(d) == datetime(2014, 6, 26, 17) 2418 2419 def testRollback2(self): 2420 assert self._offset(-3).rollback(datetime(2014, 7, 5, 15, 0)) == datetime( 2421 2014, 7, 4, 17, 0 2422 ) 2423 2424 def testRollforward1(self): 2425 assert self.offset1.rollforward(self.d) == self.d 2426 assert self.offset2.rollforward(self.d) == self.d 2427 2428 d = datetime(2014, 7, 1, 0) 2429 assert self.offset1.rollforward(d) == datetime(2014, 7, 1, 9) 2430 assert self.offset2.rollforward(d) == datetime(2014, 7, 1, 9) 2431 2432 def testRollforward2(self): 2433 assert self._offset(-3).rollforward(datetime(2014, 7, 5, 16, 0)) == datetime( 2434 2014, 7, 7, 9 2435 ) 2436 2437 def test_roll_date_object(self): 2438 offset = BusinessHour() 2439 2440 dt = datetime(2014, 7, 6, 15, 0) 2441 2442 result = offset.rollback(dt) 2443 assert result == datetime(2014, 7, 4, 17) 2444 2445 result = offset.rollforward(dt) 2446 assert result == datetime(2014, 7, 7, 9) 2447 2448 normalize_cases = [] 2449 normalize_cases.append( 2450 ( 2451 CustomBusinessHour(normalize=True, holidays=holidays), 2452 { 2453 datetime(2014, 7, 1, 8): datetime(2014, 7, 1), 2454 datetime(2014, 7, 1, 17): datetime(2014, 7, 3), 2455 datetime(2014, 7, 1, 16): datetime(2014, 7, 3), 2456 datetime(2014, 7, 1, 23): datetime(2014, 7, 3), 2457 datetime(2014, 7, 1, 0): datetime(2014, 7, 1), 2458 datetime(2014, 7, 4, 15): datetime(2014, 7, 4), 2459 datetime(2014, 7, 4, 15, 59): datetime(2014, 7, 4), 2460 datetime(2014, 7, 4, 16, 30): datetime(2014, 7, 7), 2461 datetime(2014, 7, 5, 23): datetime(2014, 7, 7), 2462 datetime(2014, 7, 6, 10): datetime(2014, 7, 7), 2463 }, 2464 ) 2465 ) 2466 2467 normalize_cases.append( 2468 ( 2469 CustomBusinessHour(-1, normalize=True, holidays=holidays), 2470 { 2471 datetime(2014, 7, 1, 8): datetime(2014, 6, 26), 2472 datetime(2014, 7, 1, 17): datetime(2014, 7, 1), 2473 datetime(2014, 7, 1, 16): datetime(2014, 7, 1), 2474 datetime(2014, 7, 1, 10): datetime(2014, 6, 26), 2475 datetime(2014, 7, 1, 0): datetime(2014, 6, 26), 2476 datetime(2014, 7, 7, 10): datetime(2014, 7, 4), 2477 datetime(2014, 7, 7, 10, 1): datetime(2014, 7, 7), 2478 datetime(2014, 7, 5, 23): datetime(2014, 7, 4), 2479 datetime(2014, 7, 6, 10): datetime(2014, 7, 4), 2480 }, 2481 ) 2482 ) 2483 2484 normalize_cases.append( 2485 ( 2486 CustomBusinessHour( 2487 1, normalize=True, start="17:00", end="04:00", holidays=holidays 2488 ), 2489 { 2490 datetime(2014, 7, 1, 8): datetime(2014, 7, 1), 2491 datetime(2014, 7, 1, 17): datetime(2014, 7, 1), 2492 datetime(2014, 7, 1, 23): datetime(2014, 7, 2), 2493 datetime(2014, 7, 2, 2): datetime(2014, 7, 2), 2494 datetime(2014, 7, 2, 3): datetime(2014, 7, 3), 2495 datetime(2014, 7, 4, 23): datetime(2014, 7, 5), 2496 datetime(2014, 7, 5, 2): datetime(2014, 7, 5), 2497 datetime(2014, 7, 7, 2): datetime(2014, 7, 7), 2498 datetime(2014, 7, 7, 17): datetime(2014, 7, 7), 2499 }, 2500 ) 2501 ) 2502 2503 @pytest.mark.parametrize("norm_cases", normalize_cases) 2504 def test_normalize(self, norm_cases): 2505 offset, cases = norm_cases 2506 for dt, expected in cases.items(): 2507 assert offset.apply(dt) == expected 2508 2509 def test_is_on_offset(self): 2510 tests = [] 2511 2512 tests.append( 2513 ( 2514 CustomBusinessHour(start="10:00", end="15:00", holidays=self.holidays), 2515 { 2516 datetime(2014, 7, 1, 9): False, 2517 datetime(2014, 7, 1, 10): True, 2518 datetime(2014, 7, 1, 15): True, 2519 datetime(2014, 7, 1, 15, 1): False, 2520 datetime(2014, 7, 5, 12): False, 2521 datetime(2014, 7, 6, 12): False, 2522 }, 2523 ) 2524 ) 2525 2526 for offset, cases in tests: 2527 for dt, expected in cases.items(): 2528 assert offset.is_on_offset(dt) == expected 2529 2530 apply_cases = [] 2531 apply_cases.append( 2532 ( 2533 CustomBusinessHour(holidays=holidays), 2534 { 2535 datetime(2014, 7, 1, 11): datetime(2014, 7, 1, 12), 2536 datetime(2014, 7, 1, 13): datetime(2014, 7, 1, 14), 2537 datetime(2014, 7, 1, 15): datetime(2014, 7, 1, 16), 2538 datetime(2014, 7, 1, 19): datetime(2014, 7, 3, 10), 2539 datetime(2014, 7, 1, 16): datetime(2014, 7, 3, 9), 2540 datetime(2014, 7, 1, 16, 30, 15): datetime(2014, 7, 3, 9, 30, 15), 2541 datetime(2014, 7, 1, 17): datetime(2014, 7, 3, 10), 2542 datetime(2014, 7, 2, 11): datetime(2014, 7, 3, 10), 2543 # out of business hours 2544 datetime(2014, 7, 2, 8): datetime(2014, 7, 3, 10), 2545 datetime(2014, 7, 2, 19): datetime(2014, 7, 3, 10), 2546 datetime(2014, 7, 2, 23): datetime(2014, 7, 3, 10), 2547 datetime(2014, 7, 3, 0): datetime(2014, 7, 3, 10), 2548 # saturday 2549 datetime(2014, 7, 5, 15): datetime(2014, 7, 7, 10), 2550 datetime(2014, 7, 4, 17): datetime(2014, 7, 7, 10), 2551 datetime(2014, 7, 4, 16, 30): datetime(2014, 7, 7, 9, 30), 2552 datetime(2014, 7, 4, 16, 30, 30): datetime(2014, 7, 7, 9, 30, 30), 2553 }, 2554 ) 2555 ) 2556 2557 apply_cases.append( 2558 ( 2559 CustomBusinessHour(4, holidays=holidays), 2560 { 2561 datetime(2014, 7, 1, 11): datetime(2014, 7, 1, 15), 2562 datetime(2014, 7, 1, 13): datetime(2014, 7, 3, 9), 2563 datetime(2014, 7, 1, 15): datetime(2014, 7, 3, 11), 2564 datetime(2014, 7, 1, 16): datetime(2014, 7, 3, 12), 2565 datetime(2014, 7, 1, 17): datetime(2014, 7, 3, 13), 2566 datetime(2014, 7, 2, 11): datetime(2014, 7, 3, 13), 2567 datetime(2014, 7, 2, 8): datetime(2014, 7, 3, 13), 2568 datetime(2014, 7, 2, 19): datetime(2014, 7, 3, 13), 2569 datetime(2014, 7, 2, 23): datetime(2014, 7, 3, 13), 2570 datetime(2014, 7, 3, 0): datetime(2014, 7, 3, 13), 2571 datetime(2014, 7, 5, 15): datetime(2014, 7, 7, 13), 2572 datetime(2014, 7, 4, 17): datetime(2014, 7, 7, 13), 2573 datetime(2014, 7, 4, 16, 30): datetime(2014, 7, 7, 12, 30), 2574 datetime(2014, 7, 4, 16, 30, 30): datetime(2014, 7, 7, 12, 30, 30), 2575 }, 2576 ) 2577 ) 2578 2579 @pytest.mark.parametrize("apply_case", apply_cases) 2580 def test_apply(self, apply_case): 2581 offset, cases = apply_case 2582 for base, expected in cases.items(): 2583 assert_offset_equal(offset, base, expected) 2584 2585 nano_cases = [] 2586 nano_cases.append( 2587 ( 2588 CustomBusinessHour(holidays=holidays), 2589 { 2590 Timestamp("2014-07-01 15:00") 2591 + Nano(5): Timestamp("2014-07-01 16:00") 2592 + Nano(5), 2593 Timestamp("2014-07-01 16:00") 2594 + Nano(5): Timestamp("2014-07-03 09:00") 2595 + Nano(5), 2596 Timestamp("2014-07-01 16:00") 2597 - Nano(5): Timestamp("2014-07-01 17:00") 2598 - Nano(5), 2599 }, 2600 ) 2601 ) 2602 2603 nano_cases.append( 2604 ( 2605 CustomBusinessHour(-1, holidays=holidays), 2606 { 2607 Timestamp("2014-07-01 15:00") 2608 + Nano(5): Timestamp("2014-07-01 14:00") 2609 + Nano(5), 2610 Timestamp("2014-07-01 10:00") 2611 + Nano(5): Timestamp("2014-07-01 09:00") 2612 + Nano(5), 2613 Timestamp("2014-07-01 10:00") 2614 - Nano(5): Timestamp("2014-06-26 17:00") 2615 - Nano(5), 2616 }, 2617 ) 2618 ) 2619 2620 @pytest.mark.parametrize("nano_case", nano_cases) 2621 def test_apply_nanoseconds(self, nano_case): 2622 offset, cases = nano_case 2623 for base, expected in cases.items(): 2624 assert_offset_equal(offset, base, expected) 2625 2626 2627class TestCustomBusinessDay(Base): 2628 _offset = CDay 2629 2630 def setup_method(self, method): 2631 self.d = datetime(2008, 1, 1) 2632 self.nd = np_datetime64_compat("2008-01-01 00:00:00Z") 2633 2634 self.offset = CDay() 2635 self.offset1 = self.offset 2636 self.offset2 = CDay(2) 2637 2638 def test_different_normalize_equals(self): 2639 # GH#21404 changed __eq__ to return False when `normalize` does not match 2640 offset = self._offset() 2641 offset2 = self._offset(normalize=True) 2642 assert offset != offset2 2643 2644 def test_repr(self): 2645 assert repr(self.offset) == "<CustomBusinessDay>" 2646 assert repr(self.offset2) == "<2 * CustomBusinessDays>" 2647 2648 expected = "<BusinessDay: offset=datetime.timedelta(days=1)>" 2649 assert repr(self.offset + timedelta(1)) == expected 2650 2651 def test_with_offset(self): 2652 offset = self.offset + timedelta(hours=2) 2653 2654 assert (self.d + offset) == datetime(2008, 1, 2, 2) 2655 2656 def test_with_offset_index(self): 2657 dti = DatetimeIndex([self.d]) 2658 result = dti + (self.offset + timedelta(hours=2)) 2659 2660 expected = DatetimeIndex([datetime(2008, 1, 2, 2)]) 2661 tm.assert_index_equal(result, expected) 2662 2663 def test_eq(self): 2664 assert self.offset2 == self.offset2 2665 2666 def test_mul(self): 2667 pass 2668 2669 def test_hash(self): 2670 assert hash(self.offset2) == hash(self.offset2) 2671 2672 def test_call(self): 2673 with tm.assert_produces_warning(FutureWarning): 2674 # GH#34171 DateOffset.__call__ is deprecated 2675 assert self.offset2(self.d) == datetime(2008, 1, 3) 2676 assert self.offset2(self.nd) == datetime(2008, 1, 3) 2677 2678 def testRollback1(self): 2679 assert CDay(10).rollback(self.d) == self.d 2680 2681 def testRollback2(self): 2682 assert CDay(10).rollback(datetime(2008, 1, 5)) == datetime(2008, 1, 4) 2683 2684 def testRollforward1(self): 2685 assert CDay(10).rollforward(self.d) == self.d 2686 2687 def testRollforward2(self): 2688 assert CDay(10).rollforward(datetime(2008, 1, 5)) == datetime(2008, 1, 7) 2689 2690 def test_roll_date_object(self): 2691 offset = CDay() 2692 2693 dt = date(2012, 9, 15) 2694 2695 result = offset.rollback(dt) 2696 assert result == datetime(2012, 9, 14) 2697 2698 result = offset.rollforward(dt) 2699 assert result == datetime(2012, 9, 17) 2700 2701 offset = offsets.Day() 2702 result = offset.rollback(dt) 2703 assert result == datetime(2012, 9, 15) 2704 2705 result = offset.rollforward(dt) 2706 assert result == datetime(2012, 9, 15) 2707 2708 on_offset_cases = [ 2709 (CDay(), datetime(2008, 1, 1), True), 2710 (CDay(), datetime(2008, 1, 5), False), 2711 ] 2712 2713 @pytest.mark.parametrize("case", on_offset_cases) 2714 def test_is_on_offset(self, case): 2715 offset, d, expected = case 2716 assert_is_on_offset(offset, d, expected) 2717 2718 apply_cases: _ApplyCases = [] 2719 apply_cases.append( 2720 ( 2721 CDay(), 2722 { 2723 datetime(2008, 1, 1): datetime(2008, 1, 2), 2724 datetime(2008, 1, 4): datetime(2008, 1, 7), 2725 datetime(2008, 1, 5): datetime(2008, 1, 7), 2726 datetime(2008, 1, 6): datetime(2008, 1, 7), 2727 datetime(2008, 1, 7): datetime(2008, 1, 8), 2728 }, 2729 ) 2730 ) 2731 2732 apply_cases.append( 2733 ( 2734 2 * CDay(), 2735 { 2736 datetime(2008, 1, 1): datetime(2008, 1, 3), 2737 datetime(2008, 1, 4): datetime(2008, 1, 8), 2738 datetime(2008, 1, 5): datetime(2008, 1, 8), 2739 datetime(2008, 1, 6): datetime(2008, 1, 8), 2740 datetime(2008, 1, 7): datetime(2008, 1, 9), 2741 }, 2742 ) 2743 ) 2744 2745 apply_cases.append( 2746 ( 2747 -CDay(), 2748 { 2749 datetime(2008, 1, 1): datetime(2007, 12, 31), 2750 datetime(2008, 1, 4): datetime(2008, 1, 3), 2751 datetime(2008, 1, 5): datetime(2008, 1, 4), 2752 datetime(2008, 1, 6): datetime(2008, 1, 4), 2753 datetime(2008, 1, 7): datetime(2008, 1, 4), 2754 datetime(2008, 1, 8): datetime(2008, 1, 7), 2755 }, 2756 ) 2757 ) 2758 2759 apply_cases.append( 2760 ( 2761 -2 * CDay(), 2762 { 2763 datetime(2008, 1, 1): datetime(2007, 12, 28), 2764 datetime(2008, 1, 4): datetime(2008, 1, 2), 2765 datetime(2008, 1, 5): datetime(2008, 1, 3), 2766 datetime(2008, 1, 6): datetime(2008, 1, 3), 2767 datetime(2008, 1, 7): datetime(2008, 1, 3), 2768 datetime(2008, 1, 8): datetime(2008, 1, 4), 2769 datetime(2008, 1, 9): datetime(2008, 1, 7), 2770 }, 2771 ) 2772 ) 2773 2774 apply_cases.append( 2775 ( 2776 CDay(0), 2777 { 2778 datetime(2008, 1, 1): datetime(2008, 1, 1), 2779 datetime(2008, 1, 4): datetime(2008, 1, 4), 2780 datetime(2008, 1, 5): datetime(2008, 1, 7), 2781 datetime(2008, 1, 6): datetime(2008, 1, 7), 2782 datetime(2008, 1, 7): datetime(2008, 1, 7), 2783 }, 2784 ) 2785 ) 2786 2787 @pytest.mark.parametrize("case", apply_cases) 2788 def test_apply(self, case): 2789 offset, cases = case 2790 for base, expected in cases.items(): 2791 assert_offset_equal(offset, base, expected) 2792 2793 def test_apply_large_n(self): 2794 dt = datetime(2012, 10, 23) 2795 2796 result = dt + CDay(10) 2797 assert result == datetime(2012, 11, 6) 2798 2799 result = dt + CDay(100) - CDay(100) 2800 assert result == dt 2801 2802 off = CDay() * 6 2803 rs = datetime(2012, 1, 1) - off 2804 xp = datetime(2011, 12, 23) 2805 assert rs == xp 2806 2807 st = datetime(2011, 12, 18) 2808 rs = st + off 2809 xp = datetime(2011, 12, 26) 2810 assert rs == xp 2811 2812 def test_apply_corner(self): 2813 msg = ( 2814 "Only know how to combine trading day " 2815 "with datetime, datetime64 or timedelta" 2816 ) 2817 with pytest.raises(ApplyTypeError, match=msg): 2818 CDay().apply(BMonthEnd()) 2819 2820 def test_holidays(self): 2821 # Define a TradingDay offset 2822 holidays = ["2012-05-01", datetime(2013, 5, 1), np.datetime64("2014-05-01")] 2823 tday = CDay(holidays=holidays) 2824 for year in range(2012, 2015): 2825 dt = datetime(year, 4, 30) 2826 xp = datetime(year, 5, 2) 2827 rs = dt + tday 2828 assert rs == xp 2829 2830 def test_weekmask(self): 2831 weekmask_saudi = "Sat Sun Mon Tue Wed" # Thu-Fri Weekend 2832 weekmask_uae = "1111001" # Fri-Sat Weekend 2833 weekmask_egypt = [1, 1, 1, 1, 0, 0, 1] # Fri-Sat Weekend 2834 bday_saudi = CDay(weekmask=weekmask_saudi) 2835 bday_uae = CDay(weekmask=weekmask_uae) 2836 bday_egypt = CDay(weekmask=weekmask_egypt) 2837 dt = datetime(2013, 5, 1) 2838 xp_saudi = datetime(2013, 5, 4) 2839 xp_uae = datetime(2013, 5, 2) 2840 xp_egypt = datetime(2013, 5, 2) 2841 assert xp_saudi == dt + bday_saudi 2842 assert xp_uae == dt + bday_uae 2843 assert xp_egypt == dt + bday_egypt 2844 xp2 = datetime(2013, 5, 5) 2845 assert xp2 == dt + 2 * bday_saudi 2846 assert xp2 == dt + 2 * bday_uae 2847 assert xp2 == dt + 2 * bday_egypt 2848 2849 def test_weekmask_and_holidays(self): 2850 weekmask_egypt = "Sun Mon Tue Wed Thu" # Fri-Sat Weekend 2851 holidays = ["2012-05-01", datetime(2013, 5, 1), np.datetime64("2014-05-01")] 2852 bday_egypt = CDay(holidays=holidays, weekmask=weekmask_egypt) 2853 dt = datetime(2013, 4, 30) 2854 xp_egypt = datetime(2013, 5, 5) 2855 assert xp_egypt == dt + 2 * bday_egypt 2856 2857 @pytest.mark.filterwarnings("ignore:Non:pandas.errors.PerformanceWarning") 2858 def test_calendar(self): 2859 calendar = USFederalHolidayCalendar() 2860 dt = datetime(2014, 1, 17) 2861 assert_offset_equal(CDay(calendar=calendar), dt, datetime(2014, 1, 21)) 2862 2863 def test_roundtrip_pickle(self): 2864 def _check_roundtrip(obj): 2865 unpickled = tm.round_trip_pickle(obj) 2866 assert unpickled == obj 2867 2868 _check_roundtrip(self.offset) 2869 _check_roundtrip(self.offset2) 2870 _check_roundtrip(self.offset * 2) 2871 2872 def test_pickle_compat_0_14_1(self, datapath): 2873 hdays = [datetime(2013, 1, 1) for ele in range(4)] 2874 pth = datapath("tseries", "offsets", "data", "cday-0.14.1.pickle") 2875 cday0_14_1 = read_pickle(pth) 2876 cday = CDay(holidays=hdays) 2877 assert cday == cday0_14_1 2878 2879 2880class CustomBusinessMonthBase: 2881 def setup_method(self, method): 2882 self.d = datetime(2008, 1, 1) 2883 2884 self.offset = self._offset() 2885 self.offset1 = self.offset 2886 self.offset2 = self._offset(2) 2887 2888 def test_eq(self): 2889 assert self.offset2 == self.offset2 2890 2891 def test_mul(self): 2892 pass 2893 2894 def test_hash(self): 2895 assert hash(self.offset2) == hash(self.offset2) 2896 2897 def test_roundtrip_pickle(self): 2898 def _check_roundtrip(obj): 2899 unpickled = tm.round_trip_pickle(obj) 2900 assert unpickled == obj 2901 2902 _check_roundtrip(self._offset()) 2903 _check_roundtrip(self._offset(2)) 2904 _check_roundtrip(self._offset() * 2) 2905 2906 def test_copy(self): 2907 # GH 17452 2908 off = self._offset(weekmask="Mon Wed Fri") 2909 assert off == off.copy() 2910 2911 2912class TestCustomBusinessMonthEnd(CustomBusinessMonthBase, Base): 2913 _offset = CBMonthEnd 2914 2915 def test_different_normalize_equals(self): 2916 # GH#21404 changed __eq__ to return False when `normalize` does not match 2917 offset = self._offset() 2918 offset2 = self._offset(normalize=True) 2919 assert offset != offset2 2920 2921 def test_repr(self): 2922 assert repr(self.offset) == "<CustomBusinessMonthEnd>" 2923 assert repr(self.offset2) == "<2 * CustomBusinessMonthEnds>" 2924 2925 def test_call(self): 2926 with tm.assert_produces_warning(FutureWarning): 2927 # GH#34171 DateOffset.__call__ is deprecated 2928 assert self.offset2(self.d) == datetime(2008, 2, 29) 2929 2930 def testRollback1(self): 2931 assert CDay(10).rollback(datetime(2007, 12, 31)) == datetime(2007, 12, 31) 2932 2933 def testRollback2(self): 2934 assert CBMonthEnd(10).rollback(self.d) == datetime(2007, 12, 31) 2935 2936 def testRollforward1(self): 2937 assert CBMonthEnd(10).rollforward(self.d) == datetime(2008, 1, 31) 2938 2939 def test_roll_date_object(self): 2940 offset = CBMonthEnd() 2941 2942 dt = date(2012, 9, 15) 2943 2944 result = offset.rollback(dt) 2945 assert result == datetime(2012, 8, 31) 2946 2947 result = offset.rollforward(dt) 2948 assert result == datetime(2012, 9, 28) 2949 2950 offset = offsets.Day() 2951 result = offset.rollback(dt) 2952 assert result == datetime(2012, 9, 15) 2953 2954 result = offset.rollforward(dt) 2955 assert result == datetime(2012, 9, 15) 2956 2957 on_offset_cases = [ 2958 (CBMonthEnd(), datetime(2008, 1, 31), True), 2959 (CBMonthEnd(), datetime(2008, 1, 1), False), 2960 ] 2961 2962 @pytest.mark.parametrize("case", on_offset_cases) 2963 def test_is_on_offset(self, case): 2964 offset, d, expected = case 2965 assert_is_on_offset(offset, d, expected) 2966 2967 apply_cases: _ApplyCases = [] 2968 apply_cases.append( 2969 ( 2970 CBMonthEnd(), 2971 { 2972 datetime(2008, 1, 1): datetime(2008, 1, 31), 2973 datetime(2008, 2, 7): datetime(2008, 2, 29), 2974 }, 2975 ) 2976 ) 2977 2978 apply_cases.append( 2979 ( 2980 2 * CBMonthEnd(), 2981 { 2982 datetime(2008, 1, 1): datetime(2008, 2, 29), 2983 datetime(2008, 2, 7): datetime(2008, 3, 31), 2984 }, 2985 ) 2986 ) 2987 2988 apply_cases.append( 2989 ( 2990 -CBMonthEnd(), 2991 { 2992 datetime(2008, 1, 1): datetime(2007, 12, 31), 2993 datetime(2008, 2, 8): datetime(2008, 1, 31), 2994 }, 2995 ) 2996 ) 2997 2998 apply_cases.append( 2999 ( 3000 -2 * CBMonthEnd(), 3001 { 3002 datetime(2008, 1, 1): datetime(2007, 11, 30), 3003 datetime(2008, 2, 9): datetime(2007, 12, 31), 3004 }, 3005 ) 3006 ) 3007 3008 apply_cases.append( 3009 ( 3010 CBMonthEnd(0), 3011 { 3012 datetime(2008, 1, 1): datetime(2008, 1, 31), 3013 datetime(2008, 2, 7): datetime(2008, 2, 29), 3014 }, 3015 ) 3016 ) 3017 3018 @pytest.mark.parametrize("case", apply_cases) 3019 def test_apply(self, case): 3020 offset, cases = case 3021 for base, expected in cases.items(): 3022 assert_offset_equal(offset, base, expected) 3023 3024 def test_apply_large_n(self): 3025 dt = datetime(2012, 10, 23) 3026 3027 result = dt + CBMonthEnd(10) 3028 assert result == datetime(2013, 7, 31) 3029 3030 result = dt + CDay(100) - CDay(100) 3031 assert result == dt 3032 3033 off = CBMonthEnd() * 6 3034 rs = datetime(2012, 1, 1) - off 3035 xp = datetime(2011, 7, 29) 3036 assert rs == xp 3037 3038 st = datetime(2011, 12, 18) 3039 rs = st + off 3040 xp = datetime(2012, 5, 31) 3041 assert rs == xp 3042 3043 def test_holidays(self): 3044 # Define a TradingDay offset 3045 holidays = ["2012-01-31", datetime(2012, 2, 28), np.datetime64("2012-02-29")] 3046 bm_offset = CBMonthEnd(holidays=holidays) 3047 dt = datetime(2012, 1, 1) 3048 assert dt + bm_offset == datetime(2012, 1, 30) 3049 assert dt + 2 * bm_offset == datetime(2012, 2, 27) 3050 3051 @pytest.mark.filterwarnings("ignore:Non:pandas.errors.PerformanceWarning") 3052 def test_datetimeindex(self): 3053 from pandas.tseries.holiday import USFederalHolidayCalendar 3054 3055 hcal = USFederalHolidayCalendar() 3056 freq = CBMonthEnd(calendar=hcal) 3057 3058 assert date_range(start="20120101", end="20130101", freq=freq).tolist()[ 3059 0 3060 ] == datetime(2012, 1, 31) 3061 3062 3063class TestCustomBusinessMonthBegin(CustomBusinessMonthBase, Base): 3064 _offset = CBMonthBegin 3065 3066 def test_different_normalize_equals(self): 3067 # GH#21404 changed __eq__ to return False when `normalize` does not match 3068 offset = self._offset() 3069 offset2 = self._offset(normalize=True) 3070 assert offset != offset2 3071 3072 def test_repr(self): 3073 assert repr(self.offset) == "<CustomBusinessMonthBegin>" 3074 assert repr(self.offset2) == "<2 * CustomBusinessMonthBegins>" 3075 3076 def test_call(self): 3077 with tm.assert_produces_warning(FutureWarning): 3078 # GH#34171 DateOffset.__call__ is deprecated 3079 assert self.offset2(self.d) == datetime(2008, 3, 3) 3080 3081 def testRollback1(self): 3082 assert CDay(10).rollback(datetime(2007, 12, 31)) == datetime(2007, 12, 31) 3083 3084 def testRollback2(self): 3085 assert CBMonthBegin(10).rollback(self.d) == datetime(2008, 1, 1) 3086 3087 def testRollforward1(self): 3088 assert CBMonthBegin(10).rollforward(self.d) == datetime(2008, 1, 1) 3089 3090 def test_roll_date_object(self): 3091 offset = CBMonthBegin() 3092 3093 dt = date(2012, 9, 15) 3094 3095 result = offset.rollback(dt) 3096 assert result == datetime(2012, 9, 3) 3097 3098 result = offset.rollforward(dt) 3099 assert result == datetime(2012, 10, 1) 3100 3101 offset = offsets.Day() 3102 result = offset.rollback(dt) 3103 assert result == datetime(2012, 9, 15) 3104 3105 result = offset.rollforward(dt) 3106 assert result == datetime(2012, 9, 15) 3107 3108 on_offset_cases = [ 3109 (CBMonthBegin(), datetime(2008, 1, 1), True), 3110 (CBMonthBegin(), datetime(2008, 1, 31), False), 3111 ] 3112 3113 @pytest.mark.parametrize("case", on_offset_cases) 3114 def test_is_on_offset(self, case): 3115 offset, dt, expected = case 3116 assert_is_on_offset(offset, dt, expected) 3117 3118 apply_cases: _ApplyCases = [] 3119 apply_cases.append( 3120 ( 3121 CBMonthBegin(), 3122 { 3123 datetime(2008, 1, 1): datetime(2008, 2, 1), 3124 datetime(2008, 2, 7): datetime(2008, 3, 3), 3125 }, 3126 ) 3127 ) 3128 3129 apply_cases.append( 3130 ( 3131 2 * CBMonthBegin(), 3132 { 3133 datetime(2008, 1, 1): datetime(2008, 3, 3), 3134 datetime(2008, 2, 7): datetime(2008, 4, 1), 3135 }, 3136 ) 3137 ) 3138 3139 apply_cases.append( 3140 ( 3141 -CBMonthBegin(), 3142 { 3143 datetime(2008, 1, 1): datetime(2007, 12, 3), 3144 datetime(2008, 2, 8): datetime(2008, 2, 1), 3145 }, 3146 ) 3147 ) 3148 3149 apply_cases.append( 3150 ( 3151 -2 * CBMonthBegin(), 3152 { 3153 datetime(2008, 1, 1): datetime(2007, 11, 1), 3154 datetime(2008, 2, 9): datetime(2008, 1, 1), 3155 }, 3156 ) 3157 ) 3158 3159 apply_cases.append( 3160 ( 3161 CBMonthBegin(0), 3162 { 3163 datetime(2008, 1, 1): datetime(2008, 1, 1), 3164 datetime(2008, 1, 7): datetime(2008, 2, 1), 3165 }, 3166 ) 3167 ) 3168 3169 @pytest.mark.parametrize("case", apply_cases) 3170 def test_apply(self, case): 3171 offset, cases = case 3172 for base, expected in cases.items(): 3173 assert_offset_equal(offset, base, expected) 3174 3175 def test_apply_large_n(self): 3176 dt = datetime(2012, 10, 23) 3177 3178 result = dt + CBMonthBegin(10) 3179 assert result == datetime(2013, 8, 1) 3180 3181 result = dt + CDay(100) - CDay(100) 3182 assert result == dt 3183 3184 off = CBMonthBegin() * 6 3185 rs = datetime(2012, 1, 1) - off 3186 xp = datetime(2011, 7, 1) 3187 assert rs == xp 3188 3189 st = datetime(2011, 12, 18) 3190 rs = st + off 3191 3192 xp = datetime(2012, 6, 1) 3193 assert rs == xp 3194 3195 def test_holidays(self): 3196 # Define a TradingDay offset 3197 holidays = ["2012-02-01", datetime(2012, 2, 2), np.datetime64("2012-03-01")] 3198 bm_offset = CBMonthBegin(holidays=holidays) 3199 dt = datetime(2012, 1, 1) 3200 3201 assert dt + bm_offset == datetime(2012, 1, 2) 3202 assert dt + 2 * bm_offset == datetime(2012, 2, 3) 3203 3204 @pytest.mark.filterwarnings("ignore:Non:pandas.errors.PerformanceWarning") 3205 def test_datetimeindex(self): 3206 hcal = USFederalHolidayCalendar() 3207 cbmb = CBMonthBegin(calendar=hcal) 3208 assert date_range(start="20120101", end="20130101", freq=cbmb).tolist()[ 3209 0 3210 ] == datetime(2012, 1, 3) 3211 3212 3213class TestWeek(Base): 3214 _offset = Week 3215 d = Timestamp(datetime(2008, 1, 2)) 3216 offset1 = _offset() 3217 offset2 = _offset(2) 3218 3219 def test_repr(self): 3220 assert repr(Week(weekday=0)) == "<Week: weekday=0>" 3221 assert repr(Week(n=-1, weekday=0)) == "<-1 * Week: weekday=0>" 3222 assert repr(Week(n=-2, weekday=0)) == "<-2 * Weeks: weekday=0>" 3223 3224 def test_corner(self): 3225 with pytest.raises(ValueError, match="Day must be"): 3226 Week(weekday=7) 3227 3228 with pytest.raises(ValueError, match="Day must be"): 3229 Week(weekday=-1) 3230 3231 def test_is_anchored(self): 3232 assert Week(weekday=0).is_anchored() 3233 assert not Week().is_anchored() 3234 assert not Week(2, weekday=2).is_anchored() 3235 assert not Week(2).is_anchored() 3236 3237 offset_cases = [] 3238 # not business week 3239 offset_cases.append( 3240 ( 3241 Week(), 3242 { 3243 datetime(2008, 1, 1): datetime(2008, 1, 8), 3244 datetime(2008, 1, 4): datetime(2008, 1, 11), 3245 datetime(2008, 1, 5): datetime(2008, 1, 12), 3246 datetime(2008, 1, 6): datetime(2008, 1, 13), 3247 datetime(2008, 1, 7): datetime(2008, 1, 14), 3248 }, 3249 ) 3250 ) 3251 3252 # Mon 3253 offset_cases.append( 3254 ( 3255 Week(weekday=0), 3256 { 3257 datetime(2007, 12, 31): datetime(2008, 1, 7), 3258 datetime(2008, 1, 4): datetime(2008, 1, 7), 3259 datetime(2008, 1, 5): datetime(2008, 1, 7), 3260 datetime(2008, 1, 6): datetime(2008, 1, 7), 3261 datetime(2008, 1, 7): datetime(2008, 1, 14), 3262 }, 3263 ) 3264 ) 3265 3266 # n=0 -> roll forward. Mon 3267 offset_cases.append( 3268 ( 3269 Week(0, weekday=0), 3270 { 3271 datetime(2007, 12, 31): datetime(2007, 12, 31), 3272 datetime(2008, 1, 4): datetime(2008, 1, 7), 3273 datetime(2008, 1, 5): datetime(2008, 1, 7), 3274 datetime(2008, 1, 6): datetime(2008, 1, 7), 3275 datetime(2008, 1, 7): datetime(2008, 1, 7), 3276 }, 3277 ) 3278 ) 3279 3280 # n=0 -> roll forward. Mon 3281 offset_cases.append( 3282 ( 3283 Week(-2, weekday=1), 3284 { 3285 datetime(2010, 4, 6): datetime(2010, 3, 23), 3286 datetime(2010, 4, 8): datetime(2010, 3, 30), 3287 datetime(2010, 4, 5): datetime(2010, 3, 23), 3288 }, 3289 ) 3290 ) 3291 3292 @pytest.mark.parametrize("case", offset_cases) 3293 def test_offset(self, case): 3294 offset, cases = case 3295 for base, expected in cases.items(): 3296 assert_offset_equal(offset, base, expected) 3297 3298 @pytest.mark.parametrize("weekday", range(7)) 3299 def test_is_on_offset(self, weekday): 3300 offset = Week(weekday=weekday) 3301 3302 for day in range(1, 8): 3303 date = datetime(2008, 1, day) 3304 3305 if day % 7 == weekday: 3306 expected = True 3307 else: 3308 expected = False 3309 assert_is_on_offset(offset, date, expected) 3310 3311 3312class TestWeekOfMonth(Base): 3313 _offset = WeekOfMonth 3314 offset1 = _offset() 3315 offset2 = _offset(2) 3316 3317 def test_constructor(self): 3318 with pytest.raises(ValueError, match="^Week"): 3319 WeekOfMonth(n=1, week=4, weekday=0) 3320 3321 with pytest.raises(ValueError, match="^Week"): 3322 WeekOfMonth(n=1, week=-1, weekday=0) 3323 3324 with pytest.raises(ValueError, match="^Day"): 3325 WeekOfMonth(n=1, week=0, weekday=-1) 3326 3327 with pytest.raises(ValueError, match="^Day"): 3328 WeekOfMonth(n=1, week=0, weekday=-7) 3329 3330 def test_repr(self): 3331 assert ( 3332 repr(WeekOfMonth(weekday=1, week=2)) == "<WeekOfMonth: week=2, weekday=1>" 3333 ) 3334 3335 def test_offset(self): 3336 date1 = datetime(2011, 1, 4) # 1st Tuesday of Month 3337 date2 = datetime(2011, 1, 11) # 2nd Tuesday of Month 3338 date3 = datetime(2011, 1, 18) # 3rd Tuesday of Month 3339 date4 = datetime(2011, 1, 25) # 4th Tuesday of Month 3340 3341 # see for loop for structure 3342 test_cases = [ 3343 (-2, 2, 1, date1, datetime(2010, 11, 16)), 3344 (-2, 2, 1, date2, datetime(2010, 11, 16)), 3345 (-2, 2, 1, date3, datetime(2010, 11, 16)), 3346 (-2, 2, 1, date4, datetime(2010, 12, 21)), 3347 (-1, 2, 1, date1, datetime(2010, 12, 21)), 3348 (-1, 2, 1, date2, datetime(2010, 12, 21)), 3349 (-1, 2, 1, date3, datetime(2010, 12, 21)), 3350 (-1, 2, 1, date4, datetime(2011, 1, 18)), 3351 (0, 0, 1, date1, datetime(2011, 1, 4)), 3352 (0, 0, 1, date2, datetime(2011, 2, 1)), 3353 (0, 0, 1, date3, datetime(2011, 2, 1)), 3354 (0, 0, 1, date4, datetime(2011, 2, 1)), 3355 (0, 1, 1, date1, datetime(2011, 1, 11)), 3356 (0, 1, 1, date2, datetime(2011, 1, 11)), 3357 (0, 1, 1, date3, datetime(2011, 2, 8)), 3358 (0, 1, 1, date4, datetime(2011, 2, 8)), 3359 (0, 0, 1, date1, datetime(2011, 1, 4)), 3360 (0, 1, 1, date2, datetime(2011, 1, 11)), 3361 (0, 2, 1, date3, datetime(2011, 1, 18)), 3362 (0, 3, 1, date4, datetime(2011, 1, 25)), 3363 (1, 0, 0, date1, datetime(2011, 2, 7)), 3364 (1, 0, 0, date2, datetime(2011, 2, 7)), 3365 (1, 0, 0, date3, datetime(2011, 2, 7)), 3366 (1, 0, 0, date4, datetime(2011, 2, 7)), 3367 (1, 0, 1, date1, datetime(2011, 2, 1)), 3368 (1, 0, 1, date2, datetime(2011, 2, 1)), 3369 (1, 0, 1, date3, datetime(2011, 2, 1)), 3370 (1, 0, 1, date4, datetime(2011, 2, 1)), 3371 (1, 0, 2, date1, datetime(2011, 1, 5)), 3372 (1, 0, 2, date2, datetime(2011, 2, 2)), 3373 (1, 0, 2, date3, datetime(2011, 2, 2)), 3374 (1, 0, 2, date4, datetime(2011, 2, 2)), 3375 (1, 2, 1, date1, datetime(2011, 1, 18)), 3376 (1, 2, 1, date2, datetime(2011, 1, 18)), 3377 (1, 2, 1, date3, datetime(2011, 2, 15)), 3378 (1, 2, 1, date4, datetime(2011, 2, 15)), 3379 (2, 2, 1, date1, datetime(2011, 2, 15)), 3380 (2, 2, 1, date2, datetime(2011, 2, 15)), 3381 (2, 2, 1, date3, datetime(2011, 3, 15)), 3382 (2, 2, 1, date4, datetime(2011, 3, 15)), 3383 ] 3384 3385 for n, week, weekday, dt, expected in test_cases: 3386 offset = WeekOfMonth(n, week=week, weekday=weekday) 3387 assert_offset_equal(offset, dt, expected) 3388 3389 # try subtracting 3390 result = datetime(2011, 2, 1) - WeekOfMonth(week=1, weekday=2) 3391 assert result == datetime(2011, 1, 12) 3392 3393 result = datetime(2011, 2, 3) - WeekOfMonth(week=0, weekday=2) 3394 assert result == datetime(2011, 2, 2) 3395 3396 on_offset_cases = [ 3397 (0, 0, datetime(2011, 2, 7), True), 3398 (0, 0, datetime(2011, 2, 6), False), 3399 (0, 0, datetime(2011, 2, 14), False), 3400 (1, 0, datetime(2011, 2, 14), True), 3401 (0, 1, datetime(2011, 2, 1), True), 3402 (0, 1, datetime(2011, 2, 8), False), 3403 ] 3404 3405 @pytest.mark.parametrize("case", on_offset_cases) 3406 def test_is_on_offset(self, case): 3407 week, weekday, dt, expected = case 3408 offset = WeekOfMonth(week=week, weekday=weekday) 3409 assert offset.is_on_offset(dt) == expected 3410 3411 3412class TestLastWeekOfMonth(Base): 3413 _offset = LastWeekOfMonth 3414 offset1 = _offset() 3415 offset2 = _offset(2) 3416 3417 def test_constructor(self): 3418 with pytest.raises(ValueError, match="^N cannot be 0"): 3419 LastWeekOfMonth(n=0, weekday=1) 3420 3421 with pytest.raises(ValueError, match="^Day"): 3422 LastWeekOfMonth(n=1, weekday=-1) 3423 3424 with pytest.raises(ValueError, match="^Day"): 3425 LastWeekOfMonth(n=1, weekday=7) 3426 3427 def test_offset(self): 3428 # Saturday 3429 last_sat = datetime(2013, 8, 31) 3430 next_sat = datetime(2013, 9, 28) 3431 offset_sat = LastWeekOfMonth(n=1, weekday=5) 3432 3433 one_day_before = last_sat + timedelta(days=-1) 3434 assert one_day_before + offset_sat == last_sat 3435 3436 one_day_after = last_sat + timedelta(days=+1) 3437 assert one_day_after + offset_sat == next_sat 3438 3439 # Test On that day 3440 assert last_sat + offset_sat == next_sat 3441 3442 # Thursday 3443 3444 offset_thur = LastWeekOfMonth(n=1, weekday=3) 3445 last_thurs = datetime(2013, 1, 31) 3446 next_thurs = datetime(2013, 2, 28) 3447 3448 one_day_before = last_thurs + timedelta(days=-1) 3449 assert one_day_before + offset_thur == last_thurs 3450 3451 one_day_after = last_thurs + timedelta(days=+1) 3452 assert one_day_after + offset_thur == next_thurs 3453 3454 # Test on that day 3455 assert last_thurs + offset_thur == next_thurs 3456 3457 three_before = last_thurs + timedelta(days=-3) 3458 assert three_before + offset_thur == last_thurs 3459 3460 two_after = last_thurs + timedelta(days=+2) 3461 assert two_after + offset_thur == next_thurs 3462 3463 offset_sunday = LastWeekOfMonth(n=1, weekday=WeekDay.SUN) 3464 assert datetime(2013, 7, 31) + offset_sunday == datetime(2013, 8, 25) 3465 3466 on_offset_cases = [ 3467 (WeekDay.SUN, datetime(2013, 1, 27), True), 3468 (WeekDay.SAT, datetime(2013, 3, 30), True), 3469 (WeekDay.MON, datetime(2013, 2, 18), False), # Not the last Mon 3470 (WeekDay.SUN, datetime(2013, 2, 25), False), # Not a SUN 3471 (WeekDay.MON, datetime(2013, 2, 25), True), 3472 (WeekDay.SAT, datetime(2013, 11, 30), True), 3473 (WeekDay.SAT, datetime(2006, 8, 26), True), 3474 (WeekDay.SAT, datetime(2007, 8, 25), True), 3475 (WeekDay.SAT, datetime(2008, 8, 30), True), 3476 (WeekDay.SAT, datetime(2009, 8, 29), True), 3477 (WeekDay.SAT, datetime(2010, 8, 28), True), 3478 (WeekDay.SAT, datetime(2011, 8, 27), True), 3479 (WeekDay.SAT, datetime(2019, 8, 31), True), 3480 ] 3481 3482 @pytest.mark.parametrize("case", on_offset_cases) 3483 def test_is_on_offset(self, case): 3484 weekday, dt, expected = case 3485 offset = LastWeekOfMonth(weekday=weekday) 3486 assert offset.is_on_offset(dt) == expected 3487 3488 def test_repr(self): 3489 assert ( 3490 repr(LastWeekOfMonth(n=2, weekday=1)) == "<2 * LastWeekOfMonths: weekday=1>" 3491 ) 3492 3493 3494class TestSemiMonthEnd(Base): 3495 _offset = SemiMonthEnd 3496 offset1 = _offset() 3497 offset2 = _offset(2) 3498 3499 def test_offset_whole_year(self): 3500 dates = ( 3501 datetime(2007, 12, 31), 3502 datetime(2008, 1, 15), 3503 datetime(2008, 1, 31), 3504 datetime(2008, 2, 15), 3505 datetime(2008, 2, 29), 3506 datetime(2008, 3, 15), 3507 datetime(2008, 3, 31), 3508 datetime(2008, 4, 15), 3509 datetime(2008, 4, 30), 3510 datetime(2008, 5, 15), 3511 datetime(2008, 5, 31), 3512 datetime(2008, 6, 15), 3513 datetime(2008, 6, 30), 3514 datetime(2008, 7, 15), 3515 datetime(2008, 7, 31), 3516 datetime(2008, 8, 15), 3517 datetime(2008, 8, 31), 3518 datetime(2008, 9, 15), 3519 datetime(2008, 9, 30), 3520 datetime(2008, 10, 15), 3521 datetime(2008, 10, 31), 3522 datetime(2008, 11, 15), 3523 datetime(2008, 11, 30), 3524 datetime(2008, 12, 15), 3525 datetime(2008, 12, 31), 3526 ) 3527 3528 for base, exp_date in zip(dates[:-1], dates[1:]): 3529 assert_offset_equal(SemiMonthEnd(), base, exp_date) 3530 3531 # ensure .apply_index works as expected 3532 s = DatetimeIndex(dates[:-1]) 3533 with tm.assert_produces_warning(None): 3534 # GH#22535 check that we don't get a FutureWarning from adding 3535 # an integer array to PeriodIndex 3536 result = SemiMonthEnd() + s 3537 3538 exp = DatetimeIndex(dates[1:]) 3539 tm.assert_index_equal(result, exp) 3540 3541 # ensure generating a range with DatetimeIndex gives same result 3542 result = date_range(start=dates[0], end=dates[-1], freq="SM") 3543 exp = DatetimeIndex(dates, freq="SM") 3544 tm.assert_index_equal(result, exp) 3545 3546 offset_cases = [] 3547 offset_cases.append( 3548 ( 3549 SemiMonthEnd(), 3550 { 3551 datetime(2008, 1, 1): datetime(2008, 1, 15), 3552 datetime(2008, 1, 15): datetime(2008, 1, 31), 3553 datetime(2008, 1, 31): datetime(2008, 2, 15), 3554 datetime(2006, 12, 14): datetime(2006, 12, 15), 3555 datetime(2006, 12, 29): datetime(2006, 12, 31), 3556 datetime(2006, 12, 31): datetime(2007, 1, 15), 3557 datetime(2007, 1, 1): datetime(2007, 1, 15), 3558 datetime(2006, 12, 1): datetime(2006, 12, 15), 3559 datetime(2006, 12, 15): datetime(2006, 12, 31), 3560 }, 3561 ) 3562 ) 3563 3564 offset_cases.append( 3565 ( 3566 SemiMonthEnd(day_of_month=20), 3567 { 3568 datetime(2008, 1, 1): datetime(2008, 1, 20), 3569 datetime(2008, 1, 15): datetime(2008, 1, 20), 3570 datetime(2008, 1, 21): datetime(2008, 1, 31), 3571 datetime(2008, 1, 31): datetime(2008, 2, 20), 3572 datetime(2006, 12, 14): datetime(2006, 12, 20), 3573 datetime(2006, 12, 29): datetime(2006, 12, 31), 3574 datetime(2006, 12, 31): datetime(2007, 1, 20), 3575 datetime(2007, 1, 1): datetime(2007, 1, 20), 3576 datetime(2006, 12, 1): datetime(2006, 12, 20), 3577 datetime(2006, 12, 15): datetime(2006, 12, 20), 3578 }, 3579 ) 3580 ) 3581 3582 offset_cases.append( 3583 ( 3584 SemiMonthEnd(0), 3585 { 3586 datetime(2008, 1, 1): datetime(2008, 1, 15), 3587 datetime(2008, 1, 16): datetime(2008, 1, 31), 3588 datetime(2008, 1, 15): datetime(2008, 1, 15), 3589 datetime(2008, 1, 31): datetime(2008, 1, 31), 3590 datetime(2006, 12, 29): datetime(2006, 12, 31), 3591 datetime(2006, 12, 31): datetime(2006, 12, 31), 3592 datetime(2007, 1, 1): datetime(2007, 1, 15), 3593 }, 3594 ) 3595 ) 3596 3597 offset_cases.append( 3598 ( 3599 SemiMonthEnd(0, day_of_month=16), 3600 { 3601 datetime(2008, 1, 1): datetime(2008, 1, 16), 3602 datetime(2008, 1, 16): datetime(2008, 1, 16), 3603 datetime(2008, 1, 15): datetime(2008, 1, 16), 3604 datetime(2008, 1, 31): datetime(2008, 1, 31), 3605 datetime(2006, 12, 29): datetime(2006, 12, 31), 3606 datetime(2006, 12, 31): datetime(2006, 12, 31), 3607 datetime(2007, 1, 1): datetime(2007, 1, 16), 3608 }, 3609 ) 3610 ) 3611 3612 offset_cases.append( 3613 ( 3614 SemiMonthEnd(2), 3615 { 3616 datetime(2008, 1, 1): datetime(2008, 1, 31), 3617 datetime(2008, 1, 31): datetime(2008, 2, 29), 3618 datetime(2006, 12, 29): datetime(2007, 1, 15), 3619 datetime(2006, 12, 31): datetime(2007, 1, 31), 3620 datetime(2007, 1, 1): datetime(2007, 1, 31), 3621 datetime(2007, 1, 16): datetime(2007, 2, 15), 3622 datetime(2006, 11, 1): datetime(2006, 11, 30), 3623 }, 3624 ) 3625 ) 3626 3627 offset_cases.append( 3628 ( 3629 SemiMonthEnd(-1), 3630 { 3631 datetime(2007, 1, 1): datetime(2006, 12, 31), 3632 datetime(2008, 6, 30): datetime(2008, 6, 15), 3633 datetime(2008, 12, 31): datetime(2008, 12, 15), 3634 datetime(2006, 12, 29): datetime(2006, 12, 15), 3635 datetime(2006, 12, 30): datetime(2006, 12, 15), 3636 datetime(2007, 1, 1): datetime(2006, 12, 31), 3637 }, 3638 ) 3639 ) 3640 3641 offset_cases.append( 3642 ( 3643 SemiMonthEnd(-1, day_of_month=4), 3644 { 3645 datetime(2007, 1, 1): datetime(2006, 12, 31), 3646 datetime(2007, 1, 4): datetime(2006, 12, 31), 3647 datetime(2008, 6, 30): datetime(2008, 6, 4), 3648 datetime(2008, 12, 31): datetime(2008, 12, 4), 3649 datetime(2006, 12, 5): datetime(2006, 12, 4), 3650 datetime(2006, 12, 30): datetime(2006, 12, 4), 3651 datetime(2007, 1, 1): datetime(2006, 12, 31), 3652 }, 3653 ) 3654 ) 3655 3656 offset_cases.append( 3657 ( 3658 SemiMonthEnd(-2), 3659 { 3660 datetime(2007, 1, 1): datetime(2006, 12, 15), 3661 datetime(2008, 6, 30): datetime(2008, 5, 31), 3662 datetime(2008, 3, 15): datetime(2008, 2, 15), 3663 datetime(2008, 12, 31): datetime(2008, 11, 30), 3664 datetime(2006, 12, 29): datetime(2006, 11, 30), 3665 datetime(2006, 12, 14): datetime(2006, 11, 15), 3666 datetime(2007, 1, 1): datetime(2006, 12, 15), 3667 }, 3668 ) 3669 ) 3670 3671 @pytest.mark.parametrize("case", offset_cases) 3672 def test_offset(self, case): 3673 offset, cases = case 3674 for base, expected in cases.items(): 3675 assert_offset_equal(offset, base, expected) 3676 3677 @pytest.mark.parametrize("case", offset_cases) 3678 def test_apply_index(self, case): 3679 # https://github.com/pandas-dev/pandas/issues/34580 3680 offset, cases = case 3681 s = DatetimeIndex(cases.keys()) 3682 exp = DatetimeIndex(cases.values()) 3683 3684 with tm.assert_produces_warning(None): 3685 # GH#22535 check that we don't get a FutureWarning from adding 3686 # an integer array to PeriodIndex 3687 result = offset + s 3688 tm.assert_index_equal(result, exp) 3689 3690 with tm.assert_produces_warning(FutureWarning): 3691 result = offset.apply_index(s) 3692 tm.assert_index_equal(result, exp) 3693 3694 on_offset_cases = [ 3695 (datetime(2007, 12, 31), True), 3696 (datetime(2007, 12, 15), True), 3697 (datetime(2007, 12, 14), False), 3698 (datetime(2007, 12, 1), False), 3699 (datetime(2008, 2, 29), True), 3700 ] 3701 3702 @pytest.mark.parametrize("case", on_offset_cases) 3703 def test_is_on_offset(self, case): 3704 dt, expected = case 3705 assert_is_on_offset(SemiMonthEnd(), dt, expected) 3706 3707 @pytest.mark.parametrize("klass", [Series, DatetimeIndex]) 3708 def test_vectorized_offset_addition(self, klass): 3709 s = klass( 3710 [ 3711 Timestamp("2000-01-15 00:15:00", tz="US/Central"), 3712 Timestamp("2000-02-15", tz="US/Central"), 3713 ], 3714 name="a", 3715 ) 3716 3717 with tm.assert_produces_warning(None): 3718 # GH#22535 check that we don't get a FutureWarning from adding 3719 # an integer array to PeriodIndex 3720 result = s + SemiMonthEnd() 3721 result2 = SemiMonthEnd() + s 3722 3723 exp = klass( 3724 [ 3725 Timestamp("2000-01-31 00:15:00", tz="US/Central"), 3726 Timestamp("2000-02-29", tz="US/Central"), 3727 ], 3728 name="a", 3729 ) 3730 tm.assert_equal(result, exp) 3731 tm.assert_equal(result2, exp) 3732 3733 s = klass( 3734 [ 3735 Timestamp("2000-01-01 00:15:00", tz="US/Central"), 3736 Timestamp("2000-02-01", tz="US/Central"), 3737 ], 3738 name="a", 3739 ) 3740 3741 with tm.assert_produces_warning(None): 3742 # GH#22535 check that we don't get a FutureWarning from adding 3743 # an integer array to PeriodIndex 3744 result = s + SemiMonthEnd() 3745 result2 = SemiMonthEnd() + s 3746 3747 exp = klass( 3748 [ 3749 Timestamp("2000-01-15 00:15:00", tz="US/Central"), 3750 Timestamp("2000-02-15", tz="US/Central"), 3751 ], 3752 name="a", 3753 ) 3754 tm.assert_equal(result, exp) 3755 tm.assert_equal(result2, exp) 3756 3757 3758class TestSemiMonthBegin(Base): 3759 _offset = SemiMonthBegin 3760 offset1 = _offset() 3761 offset2 = _offset(2) 3762 3763 def test_offset_whole_year(self): 3764 dates = ( 3765 datetime(2007, 12, 15), 3766 datetime(2008, 1, 1), 3767 datetime(2008, 1, 15), 3768 datetime(2008, 2, 1), 3769 datetime(2008, 2, 15), 3770 datetime(2008, 3, 1), 3771 datetime(2008, 3, 15), 3772 datetime(2008, 4, 1), 3773 datetime(2008, 4, 15), 3774 datetime(2008, 5, 1), 3775 datetime(2008, 5, 15), 3776 datetime(2008, 6, 1), 3777 datetime(2008, 6, 15), 3778 datetime(2008, 7, 1), 3779 datetime(2008, 7, 15), 3780 datetime(2008, 8, 1), 3781 datetime(2008, 8, 15), 3782 datetime(2008, 9, 1), 3783 datetime(2008, 9, 15), 3784 datetime(2008, 10, 1), 3785 datetime(2008, 10, 15), 3786 datetime(2008, 11, 1), 3787 datetime(2008, 11, 15), 3788 datetime(2008, 12, 1), 3789 datetime(2008, 12, 15), 3790 ) 3791 3792 for base, exp_date in zip(dates[:-1], dates[1:]): 3793 assert_offset_equal(SemiMonthBegin(), base, exp_date) 3794 3795 # ensure .apply_index works as expected 3796 s = DatetimeIndex(dates[:-1]) 3797 with tm.assert_produces_warning(None): 3798 # GH#22535 check that we don't get a FutureWarning from adding 3799 # an integer array to PeriodIndex 3800 result = SemiMonthBegin() + s 3801 3802 exp = DatetimeIndex(dates[1:]) 3803 tm.assert_index_equal(result, exp) 3804 3805 # ensure generating a range with DatetimeIndex gives same result 3806 result = date_range(start=dates[0], end=dates[-1], freq="SMS") 3807 exp = DatetimeIndex(dates, freq="SMS") 3808 tm.assert_index_equal(result, exp) 3809 3810 offset_cases = [] 3811 offset_cases.append( 3812 ( 3813 SemiMonthBegin(), 3814 { 3815 datetime(2008, 1, 1): datetime(2008, 1, 15), 3816 datetime(2008, 1, 15): datetime(2008, 2, 1), 3817 datetime(2008, 1, 31): datetime(2008, 2, 1), 3818 datetime(2006, 12, 14): datetime(2006, 12, 15), 3819 datetime(2006, 12, 29): datetime(2007, 1, 1), 3820 datetime(2006, 12, 31): datetime(2007, 1, 1), 3821 datetime(2007, 1, 1): datetime(2007, 1, 15), 3822 datetime(2006, 12, 1): datetime(2006, 12, 15), 3823 datetime(2006, 12, 15): datetime(2007, 1, 1), 3824 }, 3825 ) 3826 ) 3827 3828 offset_cases.append( 3829 ( 3830 SemiMonthBegin(day_of_month=20), 3831 { 3832 datetime(2008, 1, 1): datetime(2008, 1, 20), 3833 datetime(2008, 1, 15): datetime(2008, 1, 20), 3834 datetime(2008, 1, 21): datetime(2008, 2, 1), 3835 datetime(2008, 1, 31): datetime(2008, 2, 1), 3836 datetime(2006, 12, 14): datetime(2006, 12, 20), 3837 datetime(2006, 12, 29): datetime(2007, 1, 1), 3838 datetime(2006, 12, 31): datetime(2007, 1, 1), 3839 datetime(2007, 1, 1): datetime(2007, 1, 20), 3840 datetime(2006, 12, 1): datetime(2006, 12, 20), 3841 datetime(2006, 12, 15): datetime(2006, 12, 20), 3842 }, 3843 ) 3844 ) 3845 3846 offset_cases.append( 3847 ( 3848 SemiMonthBegin(0), 3849 { 3850 datetime(2008, 1, 1): datetime(2008, 1, 1), 3851 datetime(2008, 1, 16): datetime(2008, 2, 1), 3852 datetime(2008, 1, 15): datetime(2008, 1, 15), 3853 datetime(2008, 1, 31): datetime(2008, 2, 1), 3854 datetime(2006, 12, 29): datetime(2007, 1, 1), 3855 datetime(2006, 12, 2): datetime(2006, 12, 15), 3856 datetime(2007, 1, 1): datetime(2007, 1, 1), 3857 }, 3858 ) 3859 ) 3860 3861 offset_cases.append( 3862 ( 3863 SemiMonthBegin(0, day_of_month=16), 3864 { 3865 datetime(2008, 1, 1): datetime(2008, 1, 1), 3866 datetime(2008, 1, 16): datetime(2008, 1, 16), 3867 datetime(2008, 1, 15): datetime(2008, 1, 16), 3868 datetime(2008, 1, 31): datetime(2008, 2, 1), 3869 datetime(2006, 12, 29): datetime(2007, 1, 1), 3870 datetime(2006, 12, 31): datetime(2007, 1, 1), 3871 datetime(2007, 1, 5): datetime(2007, 1, 16), 3872 datetime(2007, 1, 1): datetime(2007, 1, 1), 3873 }, 3874 ) 3875 ) 3876 3877 offset_cases.append( 3878 ( 3879 SemiMonthBegin(2), 3880 { 3881 datetime(2008, 1, 1): datetime(2008, 2, 1), 3882 datetime(2008, 1, 31): datetime(2008, 2, 15), 3883 datetime(2006, 12, 1): datetime(2007, 1, 1), 3884 datetime(2006, 12, 29): datetime(2007, 1, 15), 3885 datetime(2006, 12, 15): datetime(2007, 1, 15), 3886 datetime(2007, 1, 1): datetime(2007, 2, 1), 3887 datetime(2007, 1, 16): datetime(2007, 2, 15), 3888 datetime(2006, 11, 1): datetime(2006, 12, 1), 3889 }, 3890 ) 3891 ) 3892 3893 offset_cases.append( 3894 ( 3895 SemiMonthBegin(-1), 3896 { 3897 datetime(2007, 1, 1): datetime(2006, 12, 15), 3898 datetime(2008, 6, 30): datetime(2008, 6, 15), 3899 datetime(2008, 6, 14): datetime(2008, 6, 1), 3900 datetime(2008, 12, 31): datetime(2008, 12, 15), 3901 datetime(2006, 12, 29): datetime(2006, 12, 15), 3902 datetime(2006, 12, 15): datetime(2006, 12, 1), 3903 datetime(2007, 1, 1): datetime(2006, 12, 15), 3904 }, 3905 ) 3906 ) 3907 3908 offset_cases.append( 3909 ( 3910 SemiMonthBegin(-1, day_of_month=4), 3911 { 3912 datetime(2007, 1, 1): datetime(2006, 12, 4), 3913 datetime(2007, 1, 4): datetime(2007, 1, 1), 3914 datetime(2008, 6, 30): datetime(2008, 6, 4), 3915 datetime(2008, 12, 31): datetime(2008, 12, 4), 3916 datetime(2006, 12, 5): datetime(2006, 12, 4), 3917 datetime(2006, 12, 30): datetime(2006, 12, 4), 3918 datetime(2006, 12, 2): datetime(2006, 12, 1), 3919 datetime(2007, 1, 1): datetime(2006, 12, 4), 3920 }, 3921 ) 3922 ) 3923 3924 offset_cases.append( 3925 ( 3926 SemiMonthBegin(-2), 3927 { 3928 datetime(2007, 1, 1): datetime(2006, 12, 1), 3929 datetime(2008, 6, 30): datetime(2008, 6, 1), 3930 datetime(2008, 6, 14): datetime(2008, 5, 15), 3931 datetime(2008, 12, 31): datetime(2008, 12, 1), 3932 datetime(2006, 12, 29): datetime(2006, 12, 1), 3933 datetime(2006, 12, 15): datetime(2006, 11, 15), 3934 datetime(2007, 1, 1): datetime(2006, 12, 1), 3935 }, 3936 ) 3937 ) 3938 3939 @pytest.mark.parametrize("case", offset_cases) 3940 def test_offset(self, case): 3941 offset, cases = case 3942 for base, expected in cases.items(): 3943 assert_offset_equal(offset, base, expected) 3944 3945 @pytest.mark.parametrize("case", offset_cases) 3946 def test_apply_index(self, case): 3947 offset, cases = case 3948 s = DatetimeIndex(cases.keys()) 3949 3950 with tm.assert_produces_warning(None): 3951 # GH#22535 check that we don't get a FutureWarning from adding 3952 # an integer array to PeriodIndex 3953 result = offset + s 3954 3955 exp = DatetimeIndex(cases.values()) 3956 tm.assert_index_equal(result, exp) 3957 3958 on_offset_cases = [ 3959 (datetime(2007, 12, 1), True), 3960 (datetime(2007, 12, 15), True), 3961 (datetime(2007, 12, 14), False), 3962 (datetime(2007, 12, 31), False), 3963 (datetime(2008, 2, 15), True), 3964 ] 3965 3966 @pytest.mark.parametrize("case", on_offset_cases) 3967 def test_is_on_offset(self, case): 3968 dt, expected = case 3969 assert_is_on_offset(SemiMonthBegin(), dt, expected) 3970 3971 @pytest.mark.parametrize("klass", [Series, DatetimeIndex]) 3972 def test_vectorized_offset_addition(self, klass): 3973 s = klass( 3974 [ 3975 Timestamp("2000-01-15 00:15:00", tz="US/Central"), 3976 Timestamp("2000-02-15", tz="US/Central"), 3977 ], 3978 name="a", 3979 ) 3980 with tm.assert_produces_warning(None): 3981 # GH#22535 check that we don't get a FutureWarning from adding 3982 # an integer array to PeriodIndex 3983 result = s + SemiMonthBegin() 3984 result2 = SemiMonthBegin() + s 3985 3986 exp = klass( 3987 [ 3988 Timestamp("2000-02-01 00:15:00", tz="US/Central"), 3989 Timestamp("2000-03-01", tz="US/Central"), 3990 ], 3991 name="a", 3992 ) 3993 tm.assert_equal(result, exp) 3994 tm.assert_equal(result2, exp) 3995 3996 s = klass( 3997 [ 3998 Timestamp("2000-01-01 00:15:00", tz="US/Central"), 3999 Timestamp("2000-02-01", tz="US/Central"), 4000 ], 4001 name="a", 4002 ) 4003 with tm.assert_produces_warning(None): 4004 # GH#22535 check that we don't get a FutureWarning from adding 4005 # an integer array to PeriodIndex 4006 result = s + SemiMonthBegin() 4007 result2 = SemiMonthBegin() + s 4008 4009 exp = klass( 4010 [ 4011 Timestamp("2000-01-15 00:15:00", tz="US/Central"), 4012 Timestamp("2000-02-15", tz="US/Central"), 4013 ], 4014 name="a", 4015 ) 4016 tm.assert_equal(result, exp) 4017 tm.assert_equal(result2, exp) 4018 4019 4020def test_Easter(): 4021 assert_offset_equal(Easter(), datetime(2010, 1, 1), datetime(2010, 4, 4)) 4022 assert_offset_equal(Easter(), datetime(2010, 4, 5), datetime(2011, 4, 24)) 4023 assert_offset_equal(Easter(2), datetime(2010, 1, 1), datetime(2011, 4, 24)) 4024 4025 assert_offset_equal(Easter(), datetime(2010, 4, 4), datetime(2011, 4, 24)) 4026 assert_offset_equal(Easter(2), datetime(2010, 4, 4), datetime(2012, 4, 8)) 4027 4028 assert_offset_equal(-Easter(), datetime(2011, 1, 1), datetime(2010, 4, 4)) 4029 assert_offset_equal(-Easter(), datetime(2010, 4, 5), datetime(2010, 4, 4)) 4030 assert_offset_equal(-Easter(2), datetime(2011, 1, 1), datetime(2009, 4, 12)) 4031 4032 assert_offset_equal(-Easter(), datetime(2010, 4, 4), datetime(2009, 4, 12)) 4033 assert_offset_equal(-Easter(2), datetime(2010, 4, 4), datetime(2008, 3, 23)) 4034 4035 4036class TestOffsetNames: 4037 def test_get_offset_name(self): 4038 assert BDay().freqstr == "B" 4039 assert BDay(2).freqstr == "2B" 4040 assert BMonthEnd().freqstr == "BM" 4041 assert Week(weekday=0).freqstr == "W-MON" 4042 assert Week(weekday=1).freqstr == "W-TUE" 4043 assert Week(weekday=2).freqstr == "W-WED" 4044 assert Week(weekday=3).freqstr == "W-THU" 4045 assert Week(weekday=4).freqstr == "W-FRI" 4046 4047 assert LastWeekOfMonth(weekday=WeekDay.SUN).freqstr == "LWOM-SUN" 4048 4049 4050def test_get_offset(): 4051 with pytest.raises(ValueError, match=INVALID_FREQ_ERR_MSG): 4052 _get_offset("gibberish") 4053 with pytest.raises(ValueError, match=INVALID_FREQ_ERR_MSG): 4054 _get_offset("QS-JAN-B") 4055 4056 pairs = [ 4057 ("B", BDay()), 4058 ("b", BDay()), 4059 ("bm", BMonthEnd()), 4060 ("Bm", BMonthEnd()), 4061 ("W-MON", Week(weekday=0)), 4062 ("W-TUE", Week(weekday=1)), 4063 ("W-WED", Week(weekday=2)), 4064 ("W-THU", Week(weekday=3)), 4065 ("W-FRI", Week(weekday=4)), 4066 ] 4067 4068 for name, expected in pairs: 4069 offset = _get_offset(name) 4070 assert offset == expected, ( 4071 f"Expected {repr(name)} to yield {repr(expected)} " 4072 f"(actual: {repr(offset)})" 4073 ) 4074 4075 4076def test_get_offset_legacy(): 4077 pairs = [("w@Sat", Week(weekday=5))] 4078 for name, expected in pairs: 4079 with pytest.raises(ValueError, match=INVALID_FREQ_ERR_MSG): 4080 _get_offset(name) 4081 4082 4083class TestOffsetAliases: 4084 def setup_method(self, method): 4085 _offset_map.clear() 4086 4087 def test_alias_equality(self): 4088 for k, v in _offset_map.items(): 4089 if v is None: 4090 continue 4091 assert k == v.copy() 4092 4093 def test_rule_code(self): 4094 lst = ["M", "MS", "BM", "BMS", "D", "B", "H", "T", "S", "L", "U"] 4095 for k in lst: 4096 assert k == _get_offset(k).rule_code 4097 # should be cached - this is kind of an internals test... 4098 assert k in _offset_map 4099 assert k == (_get_offset(k) * 3).rule_code 4100 4101 suffix_lst = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"] 4102 base = "W" 4103 for v in suffix_lst: 4104 alias = "-".join([base, v]) 4105 assert alias == _get_offset(alias).rule_code 4106 assert alias == (_get_offset(alias) * 5).rule_code 4107 4108 suffix_lst = [ 4109 "JAN", 4110 "FEB", 4111 "MAR", 4112 "APR", 4113 "MAY", 4114 "JUN", 4115 "JUL", 4116 "AUG", 4117 "SEP", 4118 "OCT", 4119 "NOV", 4120 "DEC", 4121 ] 4122 base_lst = ["A", "AS", "BA", "BAS", "Q", "QS", "BQ", "BQS"] 4123 for base in base_lst: 4124 for v in suffix_lst: 4125 alias = "-".join([base, v]) 4126 assert alias == _get_offset(alias).rule_code 4127 assert alias == (_get_offset(alias) * 5).rule_code 4128 4129 4130def test_dateoffset_misc(): 4131 oset = offsets.DateOffset(months=2, days=4) 4132 # it works 4133 oset.freqstr 4134 4135 assert not offsets.DateOffset(months=2) == 2 4136 4137 4138def test_freq_offsets(): 4139 off = BDay(1, offset=timedelta(0, 1800)) 4140 assert off.freqstr == "B+30Min" 4141 4142 off = BDay(1, offset=timedelta(0, -1800)) 4143 assert off.freqstr == "B-30Min" 4144 4145 4146class TestReprNames: 4147 def test_str_for_named_is_name(self): 4148 # look at all the amazing combinations! 4149 month_prefixes = ["A", "AS", "BA", "BAS", "Q", "BQ", "BQS", "QS"] 4150 names = [ 4151 prefix + "-" + month 4152 for prefix in month_prefixes 4153 for month in [ 4154 "JAN", 4155 "FEB", 4156 "MAR", 4157 "APR", 4158 "MAY", 4159 "JUN", 4160 "JUL", 4161 "AUG", 4162 "SEP", 4163 "OCT", 4164 "NOV", 4165 "DEC", 4166 ] 4167 ] 4168 days = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"] 4169 names += ["W-" + day for day in days] 4170 names += ["WOM-" + week + day for week in ("1", "2", "3", "4") for day in days] 4171 _offset_map.clear() 4172 for name in names: 4173 offset = _get_offset(name) 4174 assert offset.freqstr == name 4175 4176 4177def get_utc_offset_hours(ts): 4178 # take a Timestamp and compute total hours of utc offset 4179 o = ts.utcoffset() 4180 return (o.days * 24 * 3600 + o.seconds) / 3600.0 4181 4182 4183class TestDST: 4184 """ 4185 test DateOffset additions over Daylight Savings Time 4186 """ 4187 4188 # one microsecond before the DST transition 4189 ts_pre_fallback = "2013-11-03 01:59:59.999999" 4190 ts_pre_springfwd = "2013-03-10 01:59:59.999999" 4191 4192 # test both basic names and dateutil timezones 4193 timezone_utc_offsets = { 4194 "US/Eastern": {"utc_offset_daylight": -4, "utc_offset_standard": -5}, 4195 "dateutil/US/Pacific": {"utc_offset_daylight": -7, "utc_offset_standard": -8}, 4196 } 4197 valid_date_offsets_singular = [ 4198 "weekday", 4199 "day", 4200 "hour", 4201 "minute", 4202 "second", 4203 "microsecond", 4204 ] 4205 valid_date_offsets_plural = [ 4206 "weeks", 4207 "days", 4208 "hours", 4209 "minutes", 4210 "seconds", 4211 "milliseconds", 4212 "microseconds", 4213 ] 4214 4215 def _test_all_offsets(self, n, **kwds): 4216 valid_offsets = ( 4217 self.valid_date_offsets_plural 4218 if n > 1 4219 else self.valid_date_offsets_singular 4220 ) 4221 4222 for name in valid_offsets: 4223 self._test_offset(offset_name=name, offset_n=n, **kwds) 4224 4225 def _test_offset(self, offset_name, offset_n, tstart, expected_utc_offset): 4226 offset = DateOffset(**{offset_name: offset_n}) 4227 4228 t = tstart + offset 4229 if expected_utc_offset is not None: 4230 assert get_utc_offset_hours(t) == expected_utc_offset 4231 4232 if offset_name == "weeks": 4233 # dates should match 4234 assert t.date() == timedelta(days=7 * offset.kwds["weeks"]) + tstart.date() 4235 # expect the same day of week, hour of day, minute, second, ... 4236 assert ( 4237 t.dayofweek == tstart.dayofweek 4238 and t.hour == tstart.hour 4239 and t.minute == tstart.minute 4240 and t.second == tstart.second 4241 ) 4242 elif offset_name == "days": 4243 # dates should match 4244 assert timedelta(offset.kwds["days"]) + tstart.date() == t.date() 4245 # expect the same hour of day, minute, second, ... 4246 assert ( 4247 t.hour == tstart.hour 4248 and t.minute == tstart.minute 4249 and t.second == tstart.second 4250 ) 4251 elif offset_name in self.valid_date_offsets_singular: 4252 # expect the singular offset value to match between tstart and t 4253 datepart_offset = getattr( 4254 t, offset_name if offset_name != "weekday" else "dayofweek" 4255 ) 4256 assert datepart_offset == offset.kwds[offset_name] 4257 else: 4258 # the offset should be the same as if it was done in UTC 4259 assert t == (tstart.tz_convert("UTC") + offset).tz_convert("US/Pacific") 4260 4261 def _make_timestamp(self, string, hrs_offset, tz): 4262 if hrs_offset >= 0: 4263 offset_string = f"{hrs_offset:02d}00" 4264 else: 4265 offset_string = f"-{(hrs_offset * -1):02}00" 4266 return Timestamp(string + offset_string).tz_convert(tz) 4267 4268 def test_springforward_plural(self): 4269 # test moving from standard to daylight savings 4270 for tz, utc_offsets in self.timezone_utc_offsets.items(): 4271 hrs_pre = utc_offsets["utc_offset_standard"] 4272 hrs_post = utc_offsets["utc_offset_daylight"] 4273 self._test_all_offsets( 4274 n=3, 4275 tstart=self._make_timestamp(self.ts_pre_springfwd, hrs_pre, tz), 4276 expected_utc_offset=hrs_post, 4277 ) 4278 4279 def test_fallback_singular(self): 4280 # in the case of singular offsets, we don't necessarily know which utc 4281 # offset the new Timestamp will wind up in (the tz for 1 month may be 4282 # different from 1 second) so we don't specify an expected_utc_offset 4283 for tz, utc_offsets in self.timezone_utc_offsets.items(): 4284 hrs_pre = utc_offsets["utc_offset_standard"] 4285 self._test_all_offsets( 4286 n=1, 4287 tstart=self._make_timestamp(self.ts_pre_fallback, hrs_pre, tz), 4288 expected_utc_offset=None, 4289 ) 4290 4291 def test_springforward_singular(self): 4292 for tz, utc_offsets in self.timezone_utc_offsets.items(): 4293 hrs_pre = utc_offsets["utc_offset_standard"] 4294 self._test_all_offsets( 4295 n=1, 4296 tstart=self._make_timestamp(self.ts_pre_springfwd, hrs_pre, tz), 4297 expected_utc_offset=None, 4298 ) 4299 4300 offset_classes = { 4301 MonthBegin: ["11/2/2012", "12/1/2012"], 4302 MonthEnd: ["11/2/2012", "11/30/2012"], 4303 BMonthBegin: ["11/2/2012", "12/3/2012"], 4304 BMonthEnd: ["11/2/2012", "11/30/2012"], 4305 CBMonthBegin: ["11/2/2012", "12/3/2012"], 4306 CBMonthEnd: ["11/2/2012", "11/30/2012"], 4307 SemiMonthBegin: ["11/2/2012", "11/15/2012"], 4308 SemiMonthEnd: ["11/2/2012", "11/15/2012"], 4309 Week: ["11/2/2012", "11/9/2012"], 4310 YearBegin: ["11/2/2012", "1/1/2013"], 4311 YearEnd: ["11/2/2012", "12/31/2012"], 4312 BYearBegin: ["11/2/2012", "1/1/2013"], 4313 BYearEnd: ["11/2/2012", "12/31/2012"], 4314 QuarterBegin: ["11/2/2012", "12/1/2012"], 4315 QuarterEnd: ["11/2/2012", "12/31/2012"], 4316 BQuarterBegin: ["11/2/2012", "12/3/2012"], 4317 BQuarterEnd: ["11/2/2012", "12/31/2012"], 4318 Day: ["11/4/2012", "11/4/2012 23:00"], 4319 }.items() 4320 4321 @pytest.mark.parametrize("tup", offset_classes) 4322 def test_all_offset_classes(self, tup): 4323 offset, test_values = tup 4324 4325 first = Timestamp(test_values[0], tz="US/Eastern") + offset() 4326 second = Timestamp(test_values[1], tz="US/Eastern") 4327 assert first == second 4328 4329 4330# --------------------------------------------------------------------- 4331 4332 4333def test_valid_default_arguments(offset_types): 4334 # GH#19142 check that the calling the constructors without passing 4335 # any keyword arguments produce valid offsets 4336 cls = offset_types 4337 cls() 4338 4339 4340@pytest.mark.parametrize("kwd", sorted(liboffsets._relativedelta_kwds)) 4341def test_valid_month_attributes(kwd, month_classes): 4342 # GH#18226 4343 cls = month_classes 4344 # check that we cannot create e.g. MonthEnd(weeks=3) 4345 msg = rf"__init__\(\) got an unexpected keyword argument '{kwd}'" 4346 with pytest.raises(TypeError, match=msg): 4347 cls(**{kwd: 3}) 4348 4349 4350def test_month_offset_name(month_classes): 4351 # GH#33757 off.name with n != 1 should not raise AttributeError 4352 obj = month_classes(1) 4353 obj2 = month_classes(2) 4354 assert obj2.name == obj.name 4355 4356 4357@pytest.mark.parametrize("kwd", sorted(liboffsets._relativedelta_kwds)) 4358def test_valid_relativedelta_kwargs(kwd): 4359 # Check that all the arguments specified in liboffsets._relativedelta_kwds 4360 # are in fact valid relativedelta keyword args 4361 DateOffset(**{kwd: 1}) 4362 4363 4364@pytest.mark.parametrize("kwd", sorted(liboffsets._relativedelta_kwds)) 4365def test_valid_tick_attributes(kwd, tick_classes): 4366 # GH#18226 4367 cls = tick_classes 4368 # check that we cannot create e.g. Hour(weeks=3) 4369 msg = rf"__init__\(\) got an unexpected keyword argument '{kwd}'" 4370 with pytest.raises(TypeError, match=msg): 4371 cls(**{kwd: 3}) 4372 4373 4374def test_validate_n_error(): 4375 with pytest.raises(TypeError, match="argument must be an integer"): 4376 DateOffset(n="Doh!") 4377 4378 with pytest.raises(TypeError, match="argument must be an integer"): 4379 MonthBegin(n=timedelta(1)) 4380 4381 with pytest.raises(TypeError, match="argument must be an integer"): 4382 BDay(n=np.array([1, 2], dtype=np.int64)) 4383 4384 4385def test_require_integers(offset_types): 4386 cls = offset_types 4387 with pytest.raises(ValueError, match="argument must be an integer"): 4388 cls(n=1.5) 4389 4390 4391def test_tick_normalize_raises(tick_classes): 4392 # check that trying to create a Tick object with normalize=True raises 4393 # GH#21427 4394 cls = tick_classes 4395 msg = "Tick offset with `normalize=True` are not allowed." 4396 with pytest.raises(ValueError, match=msg): 4397 cls(n=3, normalize=True) 4398 4399 4400def test_weeks_onoffset(): 4401 # GH#18510 Week with weekday = None, normalize = False should always 4402 # be is_on_offset 4403 offset = Week(n=2, weekday=None) 4404 ts = Timestamp("1862-01-13 09:03:34.873477378+0210", tz="Africa/Lusaka") 4405 fast = offset.is_on_offset(ts) 4406 slow = (ts + offset) - offset == ts 4407 assert fast == slow 4408 4409 # negative n 4410 offset = Week(n=2, weekday=None) 4411 ts = Timestamp("1856-10-24 16:18:36.556360110-0717", tz="Pacific/Easter") 4412 fast = offset.is_on_offset(ts) 4413 slow = (ts + offset) - offset == ts 4414 assert fast == slow 4415 4416 4417def test_weekofmonth_onoffset(): 4418 # GH#18864 4419 # Make sure that nanoseconds don't trip up is_on_offset (and with it apply) 4420 offset = WeekOfMonth(n=2, week=2, weekday=0) 4421 ts = Timestamp("1916-05-15 01:14:49.583410462+0422", tz="Asia/Qyzylorda") 4422 fast = offset.is_on_offset(ts) 4423 slow = (ts + offset) - offset == ts 4424 assert fast == slow 4425 4426 # negative n 4427 offset = WeekOfMonth(n=-3, week=1, weekday=0) 4428 ts = Timestamp("1980-12-08 03:38:52.878321185+0500", tz="Asia/Oral") 4429 fast = offset.is_on_offset(ts) 4430 slow = (ts + offset) - offset == ts 4431 assert fast == slow 4432 4433 4434def test_last_week_of_month_on_offset(): 4435 # GH#19036, GH#18977 _adjust_dst was incorrect for LastWeekOfMonth 4436 offset = LastWeekOfMonth(n=4, weekday=6) 4437 ts = Timestamp("1917-05-27 20:55:27.084284178+0200", tz="Europe/Warsaw") 4438 slow = (ts + offset) - offset == ts 4439 fast = offset.is_on_offset(ts) 4440 assert fast == slow 4441 4442 # negative n 4443 offset = LastWeekOfMonth(n=-4, weekday=5) 4444 ts = Timestamp("2005-08-27 05:01:42.799392561-0500", tz="America/Rainy_River") 4445 slow = (ts + offset) - offset == ts 4446 fast = offset.is_on_offset(ts) 4447 assert fast == slow 4448 4449 4450def test_week_add_invalid(): 4451 # Week with weekday should raise TypeError and _not_ AttributeError 4452 # when adding invalid offset 4453 offset = Week(weekday=1) 4454 other = Day() 4455 with pytest.raises(TypeError, match="Cannot add"): 4456 offset + other 4457 4458 4459@pytest.mark.parametrize( 4460 "attribute", 4461 [ 4462 "hours", 4463 "days", 4464 "weeks", 4465 "months", 4466 "years", 4467 ], 4468) 4469def test_dateoffset_immutable(attribute): 4470 offset = DateOffset(**{attribute: 0}) 4471 msg = "DateOffset objects are immutable" 4472 with pytest.raises(AttributeError, match=msg): 4473 setattr(offset, attribute, 5) 4474