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