1from datetime import datetime, timedelta 2 3import numpy as np 4import pytest 5 6from pandas._libs.tslibs.ccalendar import DAYS, MONTHS 7from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG 8from pandas.compat import is_platform_windows 9 10from pandas import DatetimeIndex, Index, Series, Timestamp, date_range, period_range 11import pandas._testing as tm 12from pandas.core.tools.datetimes import to_datetime 13 14import pandas.tseries.frequencies as frequencies 15import pandas.tseries.offsets as offsets 16 17 18def _check_generated_range(start, periods, freq): 19 """ 20 Check the range generated from a given start, frequency, and period count. 21 22 Parameters 23 ---------- 24 start : str 25 The start date. 26 periods : int 27 The number of periods. 28 freq : str 29 The frequency of the range. 30 """ 31 freq = freq.upper() 32 33 gen = date_range(start, periods=periods, freq=freq) 34 index = DatetimeIndex(gen.values) 35 36 if not freq.startswith("Q-"): 37 assert frequencies.infer_freq(index) == gen.freqstr 38 else: 39 inf_freq = frequencies.infer_freq(index) 40 is_dec_range = inf_freq == "Q-DEC" and gen.freqstr in ( 41 "Q", 42 "Q-DEC", 43 "Q-SEP", 44 "Q-JUN", 45 "Q-MAR", 46 ) 47 is_nov_range = inf_freq == "Q-NOV" and gen.freqstr in ( 48 "Q-NOV", 49 "Q-AUG", 50 "Q-MAY", 51 "Q-FEB", 52 ) 53 is_oct_range = inf_freq == "Q-OCT" and gen.freqstr in ( 54 "Q-OCT", 55 "Q-JUL", 56 "Q-APR", 57 "Q-JAN", 58 ) 59 assert is_dec_range or is_nov_range or is_oct_range 60 61 62@pytest.fixture( 63 params=[ 64 (timedelta(1), "D"), 65 (timedelta(hours=1), "H"), 66 (timedelta(minutes=1), "T"), 67 (timedelta(seconds=1), "S"), 68 (np.timedelta64(1, "ns"), "N"), 69 (timedelta(microseconds=1), "U"), 70 (timedelta(microseconds=1000), "L"), 71 ] 72) 73def base_delta_code_pair(request): 74 return request.param 75 76 77@pytest.fixture(params=[1, 2, 3, 4]) 78def count(request): 79 return request.param 80 81 82@pytest.fixture(params=DAYS) 83def day(request): 84 return request.param 85 86 87@pytest.fixture(params=MONTHS) 88def month(request): 89 return request.param 90 91 92@pytest.fixture(params=[5, 7]) 93def periods(request): 94 return request.param 95 96 97def test_raise_if_period_index(): 98 index = period_range(start="1/1/1990", periods=20, freq="M") 99 msg = "Check the `freq` attribute instead of using infer_freq" 100 101 with pytest.raises(TypeError, match=msg): 102 frequencies.infer_freq(index) 103 104 105def test_raise_if_too_few(): 106 index = DatetimeIndex(["12/31/1998", "1/3/1999"]) 107 msg = "Need at least 3 dates to infer frequency" 108 109 with pytest.raises(ValueError, match=msg): 110 frequencies.infer_freq(index) 111 112 113def test_business_daily(): 114 index = DatetimeIndex(["01/01/1999", "1/4/1999", "1/5/1999"]) 115 assert frequencies.infer_freq(index) == "B" 116 117 118def test_business_daily_look_alike(): 119 # see gh-16624 120 # 121 # Do not infer "B when "weekend" (2-day gap) in wrong place. 122 index = DatetimeIndex(["12/31/1998", "1/3/1999", "1/4/1999"]) 123 assert frequencies.infer_freq(index) is None 124 125 126def test_day_corner(): 127 index = DatetimeIndex(["1/1/2000", "1/2/2000", "1/3/2000"]) 128 assert frequencies.infer_freq(index) == "D" 129 130 131def test_non_datetime_index(): 132 dates = to_datetime(["1/1/2000", "1/2/2000", "1/3/2000"]) 133 assert frequencies.infer_freq(dates) == "D" 134 135 136def test_fifth_week_of_month_infer(): 137 # see gh-9425 138 # 139 # Only attempt to infer up to WOM-4. 140 index = DatetimeIndex(["2014-03-31", "2014-06-30", "2015-03-30"]) 141 assert frequencies.infer_freq(index) is None 142 143 144def test_week_of_month_fake(): 145 # All of these dates are on same day 146 # of week and are 4 or 5 weeks apart. 147 index = DatetimeIndex(["2013-08-27", "2013-10-01", "2013-10-29", "2013-11-26"]) 148 assert frequencies.infer_freq(index) != "WOM-4TUE" 149 150 151def test_fifth_week_of_month(): 152 # see gh-9425 153 # 154 # Only supports freq up to WOM-4. 155 msg = ( 156 "Of the four parameters: start, end, periods, " 157 "and freq, exactly three must be specified" 158 ) 159 160 with pytest.raises(ValueError, match=msg): 161 date_range("2014-01-01", freq="WOM-5MON") 162 163 164def test_monthly_ambiguous(): 165 rng = DatetimeIndex(["1/31/2000", "2/29/2000", "3/31/2000"]) 166 assert rng.inferred_freq == "M" 167 168 169def test_annual_ambiguous(): 170 rng = DatetimeIndex(["1/31/2000", "1/31/2001", "1/31/2002"]) 171 assert rng.inferred_freq == "A-JAN" 172 173 174def test_infer_freq_delta(base_delta_code_pair, count): 175 b = Timestamp(datetime.now()) 176 base_delta, code = base_delta_code_pair 177 178 inc = base_delta * count 179 index = DatetimeIndex([b + inc * j for j in range(3)]) 180 181 exp_freq = f"{count:d}{code}" if count > 1 else code 182 assert frequencies.infer_freq(index) == exp_freq 183 184 185@pytest.mark.parametrize( 186 "constructor", 187 [ 188 lambda now, delta: DatetimeIndex( 189 [now + delta * 7] + [now + delta * j for j in range(3)] 190 ), 191 lambda now, delta: DatetimeIndex( 192 [now + delta * j for j in range(3)] + [now + delta * 7] 193 ), 194 ], 195) 196def test_infer_freq_custom(base_delta_code_pair, constructor): 197 b = Timestamp(datetime.now()) 198 base_delta, _ = base_delta_code_pair 199 200 index = constructor(b, base_delta) 201 assert frequencies.infer_freq(index) is None 202 203 204def test_weekly_infer(periods, day): 205 _check_generated_range("1/1/2000", periods, f"W-{day}") 206 207 208def test_week_of_month_infer(periods, day, count): 209 _check_generated_range("1/1/2000", periods, f"WOM-{count}{day}") 210 211 212@pytest.mark.parametrize("freq", ["M", "BM", "BMS"]) 213def test_monthly_infer(periods, freq): 214 _check_generated_range("1/1/2000", periods, "M") 215 216 217def test_quarterly_infer(month, periods): 218 _check_generated_range("1/1/2000", periods, f"Q-{month}") 219 220 221@pytest.mark.parametrize("annual", ["A", "BA"]) 222def test_annually_infer(month, periods, annual): 223 _check_generated_range("1/1/2000", periods, f"{annual}-{month}") 224 225 226@pytest.mark.parametrize( 227 "freq,expected", [("Q", "Q-DEC"), ("Q-NOV", "Q-NOV"), ("Q-OCT", "Q-OCT")] 228) 229def test_infer_freq_index(freq, expected): 230 rng = period_range("1959Q2", "2009Q3", freq=freq) 231 rng = Index(rng.to_timestamp("D", how="e").astype(object)) 232 233 assert rng.inferred_freq == expected 234 235 236@pytest.mark.parametrize( 237 "expected,dates", 238 list( 239 { 240 "AS-JAN": ["2009-01-01", "2010-01-01", "2011-01-01", "2012-01-01"], 241 "Q-OCT": ["2009-01-31", "2009-04-30", "2009-07-31", "2009-10-31"], 242 "M": ["2010-11-30", "2010-12-31", "2011-01-31", "2011-02-28"], 243 "W-SAT": ["2010-12-25", "2011-01-01", "2011-01-08", "2011-01-15"], 244 "D": ["2011-01-01", "2011-01-02", "2011-01-03", "2011-01-04"], 245 "H": [ 246 "2011-12-31 22:00", 247 "2011-12-31 23:00", 248 "2012-01-01 00:00", 249 "2012-01-01 01:00", 250 ], 251 }.items() 252 ), 253) 254def test_infer_freq_tz(tz_naive_fixture, expected, dates): 255 # see gh-7310 256 tz = tz_naive_fixture 257 idx = DatetimeIndex(dates, tz=tz) 258 assert idx.inferred_freq == expected 259 260 261@pytest.mark.parametrize( 262 "date_pair", 263 [ 264 ["2013-11-02", "2013-11-5"], # Fall DST 265 ["2014-03-08", "2014-03-11"], # Spring DST 266 ["2014-01-01", "2014-01-03"], # Regular Time 267 ], 268) 269@pytest.mark.parametrize( 270 "freq", ["3H", "10T", "3601S", "3600001L", "3600000001U", "3600000000001N"] 271) 272def test_infer_freq_tz_transition(tz_naive_fixture, date_pair, freq): 273 # see gh-8772 274 tz = tz_naive_fixture 275 idx = date_range(date_pair[0], date_pair[1], freq=freq, tz=tz) 276 assert idx.inferred_freq == freq 277 278 279def test_infer_freq_tz_transition_custom(): 280 index = date_range("2013-11-03", periods=5, freq="3H").tz_localize( 281 "America/Chicago" 282 ) 283 assert index.inferred_freq is None 284 285 286@pytest.mark.parametrize( 287 "data,expected", 288 [ 289 # Hourly freq in a day must result in "H" 290 ( 291 [ 292 "2014-07-01 09:00", 293 "2014-07-01 10:00", 294 "2014-07-01 11:00", 295 "2014-07-01 12:00", 296 "2014-07-01 13:00", 297 "2014-07-01 14:00", 298 ], 299 "H", 300 ), 301 ( 302 [ 303 "2014-07-01 09:00", 304 "2014-07-01 10:00", 305 "2014-07-01 11:00", 306 "2014-07-01 12:00", 307 "2014-07-01 13:00", 308 "2014-07-01 14:00", 309 "2014-07-01 15:00", 310 "2014-07-01 16:00", 311 "2014-07-02 09:00", 312 "2014-07-02 10:00", 313 "2014-07-02 11:00", 314 ], 315 "BH", 316 ), 317 ( 318 [ 319 "2014-07-04 09:00", 320 "2014-07-04 10:00", 321 "2014-07-04 11:00", 322 "2014-07-04 12:00", 323 "2014-07-04 13:00", 324 "2014-07-04 14:00", 325 "2014-07-04 15:00", 326 "2014-07-04 16:00", 327 "2014-07-07 09:00", 328 "2014-07-07 10:00", 329 "2014-07-07 11:00", 330 ], 331 "BH", 332 ), 333 ( 334 [ 335 "2014-07-04 09:00", 336 "2014-07-04 10:00", 337 "2014-07-04 11:00", 338 "2014-07-04 12:00", 339 "2014-07-04 13:00", 340 "2014-07-04 14:00", 341 "2014-07-04 15:00", 342 "2014-07-04 16:00", 343 "2014-07-07 09:00", 344 "2014-07-07 10:00", 345 "2014-07-07 11:00", 346 "2014-07-07 12:00", 347 "2014-07-07 13:00", 348 "2014-07-07 14:00", 349 "2014-07-07 15:00", 350 "2014-07-07 16:00", 351 "2014-07-08 09:00", 352 "2014-07-08 10:00", 353 "2014-07-08 11:00", 354 "2014-07-08 12:00", 355 "2014-07-08 13:00", 356 "2014-07-08 14:00", 357 "2014-07-08 15:00", 358 "2014-07-08 16:00", 359 ], 360 "BH", 361 ), 362 ], 363) 364def test_infer_freq_business_hour(data, expected): 365 # see gh-7905 366 idx = DatetimeIndex(data) 367 assert idx.inferred_freq == expected 368 369 370def test_not_monotonic(): 371 rng = DatetimeIndex(["1/31/2000", "1/31/2001", "1/31/2002"]) 372 rng = rng[::-1] 373 374 assert rng.inferred_freq == "-1A-JAN" 375 376 377def test_non_datetime_index2(): 378 rng = DatetimeIndex(["1/31/2000", "1/31/2001", "1/31/2002"]) 379 vals = rng.to_pydatetime() 380 381 result = frequencies.infer_freq(vals) 382 assert result == rng.inferred_freq 383 384 385@pytest.mark.parametrize( 386 "idx", [tm.makeIntIndex(10), tm.makeFloatIndex(10), tm.makePeriodIndex(10)] 387) 388def test_invalid_index_types(idx): 389 msg = ( 390 "(cannot infer freq from a non-convertible)|" 391 "(Check the `freq` attribute instead of using infer_freq)" 392 ) 393 394 with pytest.raises(TypeError, match=msg): 395 frequencies.infer_freq(idx) 396 397 398@pytest.mark.skipif(is_platform_windows(), reason="see gh-10822: Windows issue") 399@pytest.mark.parametrize("idx", [tm.makeStringIndex(10), tm.makeUnicodeIndex(10)]) 400def test_invalid_index_types_unicode(idx): 401 # see gh-10822 402 # 403 # Odd error message on conversions to datetime for unicode. 404 msg = "Unknown string format" 405 406 with pytest.raises(ValueError, match=msg): 407 frequencies.infer_freq(idx) 408 409 410def test_string_datetime_like_compat(): 411 # see gh-6463 412 data = ["2004-01", "2004-02", "2004-03", "2004-04"] 413 414 expected = frequencies.infer_freq(data) 415 result = frequencies.infer_freq(Index(data)) 416 417 assert result == expected 418 419 420def test_series(): 421 # see gh-6407 422 s = Series(date_range("20130101", "20130110")) 423 inferred = frequencies.infer_freq(s) 424 assert inferred == "D" 425 426 427@pytest.mark.parametrize("end", [10, 10.0]) 428def test_series_invalid_type(end): 429 # see gh-6407 430 msg = "cannot infer freq from a non-convertible dtype on a Series" 431 s = Series(np.arange(end)) 432 433 with pytest.raises(TypeError, match=msg): 434 frequencies.infer_freq(s) 435 436 437def test_series_inconvertible_string(): 438 # see gh-6407 439 msg = "Unknown string format" 440 441 with pytest.raises(ValueError, match=msg): 442 frequencies.infer_freq(Series(["foo", "bar"])) 443 444 445@pytest.mark.parametrize("freq", [None, "L"]) 446def test_series_period_index(freq): 447 # see gh-6407 448 # 449 # Cannot infer on PeriodIndex 450 msg = "cannot infer freq from a non-convertible dtype on a Series" 451 s = Series(period_range("2013", periods=10, freq=freq)) 452 453 with pytest.raises(TypeError, match=msg): 454 frequencies.infer_freq(s) 455 456 457@pytest.mark.parametrize("freq", ["M", "L", "S"]) 458def test_series_datetime_index(freq): 459 s = Series(date_range("20130101", periods=10, freq=freq)) 460 inferred = frequencies.infer_freq(s) 461 assert inferred == freq 462 463 464@pytest.mark.parametrize( 465 "offset_func", 466 [ 467 frequencies._get_offset, 468 lambda freq: date_range("2011-01-01", periods=5, freq=freq), 469 ], 470) 471@pytest.mark.parametrize( 472 "freq", 473 [ 474 "WEEKDAY", 475 "EOM", 476 "W@MON", 477 "W@TUE", 478 "W@WED", 479 "W@THU", 480 "W@FRI", 481 "W@SAT", 482 "W@SUN", 483 "Q@JAN", 484 "Q@FEB", 485 "Q@MAR", 486 "A@JAN", 487 "A@FEB", 488 "A@MAR", 489 "A@APR", 490 "A@MAY", 491 "A@JUN", 492 "A@JUL", 493 "A@AUG", 494 "A@SEP", 495 "A@OCT", 496 "A@NOV", 497 "A@DEC", 498 "Y@JAN", 499 "WOM@1MON", 500 "WOM@2MON", 501 "WOM@3MON", 502 "WOM@4MON", 503 "WOM@1TUE", 504 "WOM@2TUE", 505 "WOM@3TUE", 506 "WOM@4TUE", 507 "WOM@1WED", 508 "WOM@2WED", 509 "WOM@3WED", 510 "WOM@4WED", 511 "WOM@1THU", 512 "WOM@2THU", 513 "WOM@3THU", 514 "WOM@4THU", 515 "WOM@1FRI", 516 "WOM@2FRI", 517 "WOM@3FRI", 518 "WOM@4FRI", 519 ], 520) 521def test_legacy_offset_warnings(offset_func, freq): 522 with pytest.raises(ValueError, match=INVALID_FREQ_ERR_MSG): 523 offset_func(freq) 524 525 526def test_ms_vs_capital_ms(): 527 left = frequencies._get_offset("ms") 528 right = frequencies._get_offset("MS") 529 530 assert left == offsets.Milli() 531 assert right == offsets.MonthBegin() 532