1"""
2Tests for offsets.Tick and subclasses
3"""
4from datetime import datetime, timedelta
5
6from hypothesis import assume, example, given, settings, strategies as st
7import numpy as np
8import pytest
9
10from pandas._libs.tslibs.offsets import delta_to_tick
11
12from pandas import Timedelta, Timestamp
13import pandas._testing as tm
14
15from pandas.tseries import offsets
16from pandas.tseries.offsets import Hour, Micro, Milli, Minute, Nano, Second
17
18from .common import assert_offset_equal
19
20# ---------------------------------------------------------------------
21# Test Helpers
22
23tick_classes = [Hour, Minute, Second, Milli, Micro, Nano]
24
25
26# ---------------------------------------------------------------------
27
28
29def test_apply_ticks():
30    result = offsets.Hour(3).apply(offsets.Hour(4))
31    exp = offsets.Hour(7)
32    assert result == exp
33
34
35def test_delta_to_tick():
36    delta = timedelta(3)
37
38    tick = delta_to_tick(delta)
39    assert tick == offsets.Day(3)
40
41    td = Timedelta(nanoseconds=5)
42    tick = delta_to_tick(td)
43    assert tick == Nano(5)
44
45
46@pytest.mark.parametrize("cls", tick_classes)
47@settings(deadline=None)  # GH 24641
48@example(n=2, m=3)
49@example(n=800, m=300)
50@example(n=1000, m=5)
51@given(n=st.integers(-999, 999), m=st.integers(-999, 999))
52def test_tick_add_sub(cls, n, m):
53    # For all Tick subclasses and all integers n, m, we should have
54    # tick(n) + tick(m) == tick(n+m)
55    # tick(n) - tick(m) == tick(n-m)
56    left = cls(n)
57    right = cls(m)
58    expected = cls(n + m)
59
60    assert left + right == expected
61    assert left.apply(right) == expected
62
63    expected = cls(n - m)
64    assert left - right == expected
65
66
67@pytest.mark.arm_slow
68@pytest.mark.parametrize("cls", tick_classes)
69@settings(deadline=None)
70@example(n=2, m=3)
71@given(n=st.integers(-999, 999), m=st.integers(-999, 999))
72def test_tick_equality(cls, n, m):
73    assume(m != n)
74    # tick == tock iff tick.n == tock.n
75    left = cls(n)
76    right = cls(m)
77    assert left != right
78    assert not (left == right)
79
80    right = cls(n)
81    assert left == right
82    assert not (left != right)
83
84    if n != 0:
85        assert cls(n) != cls(-n)
86
87
88# ---------------------------------------------------------------------
89
90
91def test_Hour():
92    assert_offset_equal(Hour(), datetime(2010, 1, 1), datetime(2010, 1, 1, 1))
93    assert_offset_equal(Hour(-1), datetime(2010, 1, 1, 1), datetime(2010, 1, 1))
94    assert_offset_equal(2 * Hour(), datetime(2010, 1, 1), datetime(2010, 1, 1, 2))
95    assert_offset_equal(-1 * Hour(), datetime(2010, 1, 1, 1), datetime(2010, 1, 1))
96
97    assert Hour(3) + Hour(2) == Hour(5)
98    assert Hour(3) - Hour(2) == Hour()
99
100    assert Hour(4) != Hour(1)
101
102
103def test_Minute():
104    assert_offset_equal(Minute(), datetime(2010, 1, 1), datetime(2010, 1, 1, 0, 1))
105    assert_offset_equal(Minute(-1), datetime(2010, 1, 1, 0, 1), datetime(2010, 1, 1))
106    assert_offset_equal(2 * Minute(), datetime(2010, 1, 1), datetime(2010, 1, 1, 0, 2))
107    assert_offset_equal(-1 * Minute(), datetime(2010, 1, 1, 0, 1), datetime(2010, 1, 1))
108
109    assert Minute(3) + Minute(2) == Minute(5)
110    assert Minute(3) - Minute(2) == Minute()
111    assert Minute(5) != Minute()
112
113
114def test_Second():
115    assert_offset_equal(Second(), datetime(2010, 1, 1), datetime(2010, 1, 1, 0, 0, 1))
116    assert_offset_equal(Second(-1), datetime(2010, 1, 1, 0, 0, 1), datetime(2010, 1, 1))
117    assert_offset_equal(
118        2 * Second(), datetime(2010, 1, 1), datetime(2010, 1, 1, 0, 0, 2)
119    )
120    assert_offset_equal(
121        -1 * Second(), datetime(2010, 1, 1, 0, 0, 1), datetime(2010, 1, 1)
122    )
123
124    assert Second(3) + Second(2) == Second(5)
125    assert Second(3) - Second(2) == Second()
126
127
128def test_Millisecond():
129    assert_offset_equal(
130        Milli(), datetime(2010, 1, 1), datetime(2010, 1, 1, 0, 0, 0, 1000)
131    )
132    assert_offset_equal(
133        Milli(-1), datetime(2010, 1, 1, 0, 0, 0, 1000), datetime(2010, 1, 1)
134    )
135    assert_offset_equal(
136        Milli(2), datetime(2010, 1, 1), datetime(2010, 1, 1, 0, 0, 0, 2000)
137    )
138    assert_offset_equal(
139        2 * Milli(), datetime(2010, 1, 1), datetime(2010, 1, 1, 0, 0, 0, 2000)
140    )
141    assert_offset_equal(
142        -1 * Milli(), datetime(2010, 1, 1, 0, 0, 0, 1000), datetime(2010, 1, 1)
143    )
144
145    assert Milli(3) + Milli(2) == Milli(5)
146    assert Milli(3) - Milli(2) == Milli()
147
148
149def test_MillisecondTimestampArithmetic():
150    assert_offset_equal(
151        Milli(), Timestamp("2010-01-01"), Timestamp("2010-01-01 00:00:00.001")
152    )
153    assert_offset_equal(
154        Milli(-1), Timestamp("2010-01-01 00:00:00.001"), Timestamp("2010-01-01")
155    )
156
157
158def test_Microsecond():
159    assert_offset_equal(Micro(), datetime(2010, 1, 1), datetime(2010, 1, 1, 0, 0, 0, 1))
160    assert_offset_equal(
161        Micro(-1), datetime(2010, 1, 1, 0, 0, 0, 1), datetime(2010, 1, 1)
162    )
163
164    assert_offset_equal(
165        2 * Micro(), datetime(2010, 1, 1), datetime(2010, 1, 1, 0, 0, 0, 2)
166    )
167    assert_offset_equal(
168        -1 * Micro(), datetime(2010, 1, 1, 0, 0, 0, 1), datetime(2010, 1, 1)
169    )
170
171    assert Micro(3) + Micro(2) == Micro(5)
172    assert Micro(3) - Micro(2) == Micro()
173
174
175def test_NanosecondGeneric():
176    timestamp = Timestamp(datetime(2010, 1, 1))
177    assert timestamp.nanosecond == 0
178
179    result = timestamp + Nano(10)
180    assert result.nanosecond == 10
181
182    reverse_result = Nano(10) + timestamp
183    assert reverse_result.nanosecond == 10
184
185
186def test_Nanosecond():
187    timestamp = Timestamp(datetime(2010, 1, 1))
188    assert_offset_equal(Nano(), timestamp, timestamp + np.timedelta64(1, "ns"))
189    assert_offset_equal(Nano(-1), timestamp + np.timedelta64(1, "ns"), timestamp)
190    assert_offset_equal(2 * Nano(), timestamp, timestamp + np.timedelta64(2, "ns"))
191    assert_offset_equal(-1 * Nano(), timestamp + np.timedelta64(1, "ns"), timestamp)
192
193    assert Nano(3) + Nano(2) == Nano(5)
194    assert Nano(3) - Nano(2) == Nano()
195
196    # GH9284
197    assert Nano(1) + Nano(10) == Nano(11)
198    assert Nano(5) + Micro(1) == Nano(1005)
199    assert Micro(5) + Nano(1) == Nano(5001)
200
201
202@pytest.mark.parametrize(
203    "kls, expected",
204    [
205        (Hour, Timedelta(hours=5)),
206        (Minute, Timedelta(hours=2, minutes=3)),
207        (Second, Timedelta(hours=2, seconds=3)),
208        (Milli, Timedelta(hours=2, milliseconds=3)),
209        (Micro, Timedelta(hours=2, microseconds=3)),
210        (Nano, Timedelta(hours=2, nanoseconds=3)),
211    ],
212)
213def test_tick_addition(kls, expected):
214    offset = kls(3)
215    result = offset + Timedelta(hours=2)
216    assert isinstance(result, Timedelta)
217    assert result == expected
218
219
220@pytest.mark.parametrize("cls", tick_classes)
221def test_tick_division(cls):
222    off = cls(10)
223
224    assert off / cls(5) == 2
225    assert off / 2 == cls(5)
226    assert off / 2.0 == cls(5)
227
228    assert off / off.delta == 1
229    assert off / off.delta.to_timedelta64() == 1
230
231    assert off / Nano(1) == off.delta / Nano(1).delta
232
233    if cls is not Nano:
234        # A case where we end up with a smaller class
235        result = off / 1000
236        assert isinstance(result, offsets.Tick)
237        assert not isinstance(result, cls)
238        assert result.delta == off.delta / 1000
239
240    if cls._nanos_inc < Timedelta(seconds=1).value:
241        # Case where we end up with a bigger class
242        result = off / 0.001
243        assert isinstance(result, offsets.Tick)
244        assert not isinstance(result, cls)
245        assert result.delta == off.delta / 0.001
246
247
248def test_tick_mul_float():
249    off = Micro(2)
250
251    # Case where we retain type
252    result = off * 1.5
253    expected = Micro(3)
254    assert result == expected
255    assert isinstance(result, Micro)
256
257    # Case where we bump up to the next type
258    result = off * 1.25
259    expected = Nano(2500)
260    assert result == expected
261    assert isinstance(result, Nano)
262
263
264@pytest.mark.parametrize("cls", tick_classes)
265def test_tick_rdiv(cls):
266    off = cls(10)
267    delta = off.delta
268    td64 = delta.to_timedelta64()
269    instance__type = ".".join([cls.__module__, cls.__name__])
270    msg = (
271        "unsupported operand type\\(s\\) for \\/: 'int'|'float' and "
272        f"'{instance__type}'"
273    )
274
275    with pytest.raises(TypeError, match=msg):
276        2 / off
277    with pytest.raises(TypeError, match=msg):
278        2.0 / off
279
280    assert (td64 * 2.5) / off == 2.5
281
282    if cls is not Nano:
283        # skip pytimedelta for Nano since it gets dropped
284        assert (delta.to_pytimedelta() * 2) / off == 2
285
286    result = np.array([2 * td64, td64]) / off
287    expected = np.array([2.0, 1.0])
288    tm.assert_numpy_array_equal(result, expected)
289
290
291@pytest.mark.parametrize("cls1", tick_classes)
292@pytest.mark.parametrize("cls2", tick_classes)
293def test_tick_zero(cls1, cls2):
294    assert cls1(0) == cls2(0)
295    assert cls1(0) + cls2(0) == cls1(0)
296
297    if cls1 is not Nano:
298        assert cls1(2) + cls2(0) == cls1(2)
299
300    if cls1 is Nano:
301        assert cls1(2) + Nano(0) == cls1(2)
302
303
304@pytest.mark.parametrize("cls", tick_classes)
305def test_tick_equalities(cls):
306    assert cls() == cls(1)
307
308
309@pytest.mark.parametrize("cls", tick_classes)
310def test_tick_offset(cls):
311    assert not cls().is_anchored()
312
313
314@pytest.mark.parametrize("cls", tick_classes)
315def test_compare_ticks(cls):
316    three = cls(3)
317    four = cls(4)
318
319    assert three < cls(4)
320    assert cls(3) < four
321    assert four > cls(3)
322    assert cls(4) > three
323    assert cls(3) == cls(3)
324    assert cls(3) != cls(4)
325
326
327@pytest.mark.parametrize("cls", tick_classes)
328def test_compare_ticks_to_strs(cls):
329    # GH#23524
330    off = cls(19)
331
332    # These tests should work with any strings, but we particularly are
333    #  interested in "infer" as that comparison is convenient to make in
334    #  Datetime/Timedelta Array/Index constructors
335    assert not off == "infer"
336    assert not "foo" == off
337
338    instance_type = ".".join([cls.__module__, cls.__name__])
339    msg = (
340        "'<'|'<='|'>'|'>=' not supported between instances of "
341        f"'str' and '{instance_type}'|'{instance_type}' and 'str'"
342    )
343
344    for left, right in [("infer", off), (off, "infer")]:
345        with pytest.raises(TypeError, match=msg):
346            left < right
347        with pytest.raises(TypeError, match=msg):
348            left <= right
349        with pytest.raises(TypeError, match=msg):
350            left > right
351        with pytest.raises(TypeError, match=msg):
352            left >= right
353
354
355@pytest.mark.parametrize("cls", tick_classes)
356def test_compare_ticks_to_timedeltalike(cls):
357    off = cls(19)
358
359    td = off.delta
360
361    others = [td, td.to_timedelta64()]
362    if cls is not Nano:
363        others.append(td.to_pytimedelta())
364
365    for other in others:
366        assert off == other
367        assert not off != other
368        assert not off < other
369        assert not off > other
370        assert off <= other
371        assert off >= other
372