1""" 2Behavioral based tests for offsets and date_range. 3 4This file is adapted from https://github.com/pandas-dev/pandas/pull/18761 - 5which was more ambitious but less idiomatic in its use of Hypothesis. 6 7You may wish to consult the previous version for inspiration on further 8tests, or when trying to pin down the bugs exposed by the tests below. 9""" 10import warnings 11 12from hypothesis import assume, given, strategies as st 13from hypothesis.errors import Flaky 14from hypothesis.extra.dateutil import timezones as dateutil_timezones 15from hypothesis.extra.pytz import timezones as pytz_timezones 16import pytest 17import pytz 18 19import pandas as pd 20from pandas import Timestamp 21 22from pandas.tseries.offsets import ( 23 BMonthBegin, 24 BMonthEnd, 25 BQuarterBegin, 26 BQuarterEnd, 27 BYearBegin, 28 BYearEnd, 29 MonthBegin, 30 MonthEnd, 31 QuarterBegin, 32 QuarterEnd, 33 YearBegin, 34 YearEnd, 35) 36 37# ---------------------------------------------------------------- 38# Helpers for generating random data 39 40with warnings.catch_warnings(): 41 warnings.simplefilter("ignore") 42 min_dt = Timestamp(1900, 1, 1).to_pydatetime() 43 max_dt = Timestamp(1900, 1, 1).to_pydatetime() 44 45gen_date_range = st.builds( 46 pd.date_range, 47 start=st.datetimes( 48 # TODO: Choose the min/max values more systematically 49 min_value=Timestamp(1900, 1, 1).to_pydatetime(), 50 max_value=Timestamp(2100, 1, 1).to_pydatetime(), 51 ), 52 periods=st.integers(min_value=2, max_value=100), 53 freq=st.sampled_from("Y Q M D H T s ms us ns".split()), 54 tz=st.one_of(st.none(), dateutil_timezones(), pytz_timezones()), 55) 56 57gen_random_datetime = st.datetimes( 58 min_value=min_dt, 59 max_value=max_dt, 60 timezones=st.one_of(st.none(), dateutil_timezones(), pytz_timezones()), 61) 62 63# The strategy for each type is registered in conftest.py, as they don't carry 64# enough runtime information (e.g. type hints) to infer how to build them. 65gen_yqm_offset = st.one_of( 66 *map( 67 st.from_type, 68 [ 69 MonthBegin, 70 MonthEnd, 71 BMonthBegin, 72 BMonthEnd, 73 QuarterBegin, 74 QuarterEnd, 75 BQuarterBegin, 76 BQuarterEnd, 77 YearBegin, 78 YearEnd, 79 BYearBegin, 80 BYearEnd, 81 ], 82 ) 83) 84 85 86# ---------------------------------------------------------------- 87# Offset-specific behaviour tests 88 89 90@pytest.mark.arm_slow 91@given(gen_random_datetime, gen_yqm_offset) 92def test_on_offset_implementations(dt, offset): 93 assume(not offset.normalize) 94 # check that the class-specific implementations of is_on_offset match 95 # the general case definition: 96 # (dt + offset) - offset == dt 97 try: 98 compare = (dt + offset) - offset 99 except pytz.NonExistentTimeError: 100 # dt + offset does not exist, assume(False) to indicate 101 # to hypothesis that this is not a valid test case 102 assume(False) 103 104 assert offset.is_on_offset(dt) == (compare == dt) 105 106 107@pytest.mark.xfail(strict=False, raises=Flaky, reason="unreliable test timings") 108@given(gen_yqm_offset) 109def test_shift_across_dst(offset): 110 # GH#18319 check that 1) timezone is correctly normalized and 111 # 2) that hour is not incorrectly changed by this normalization 112 assume(not offset.normalize) 113 114 # Note that dti includes a transition across DST boundary 115 dti = pd.date_range( 116 start="2017-10-30 12:00:00", end="2017-11-06", freq="D", tz="US/Eastern" 117 ) 118 assert (dti.hour == 12).all() # we haven't screwed up yet 119 120 res = dti + offset 121 assert (res.hour == 12).all() 122