hyb
2025-10-24 6861b499efcd43195796ee314c96124b34d1a327
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
from hypothesis import (
    given,
    strategies as st,
)
import numpy as np
import pytest
 
from pandas._libs import lib
from pandas._libs.tslibs import iNaT
from pandas.errors import OutOfBoundsTimedelta
 
from pandas import Timedelta
 
 
class TestTimedeltaRound:
    @pytest.mark.parametrize(
        "freq,s1,s2",
        [
            # This first case has s1, s2 being the same as t1,t2 below
            (
                "ns",
                Timedelta("1 days 02:34:56.789123456"),
                Timedelta("-1 days 02:34:56.789123456"),
            ),
            (
                "us",
                Timedelta("1 days 02:34:56.789123000"),
                Timedelta("-1 days 02:34:56.789123000"),
            ),
            (
                "ms",
                Timedelta("1 days 02:34:56.789000000"),
                Timedelta("-1 days 02:34:56.789000000"),
            ),
            ("s", Timedelta("1 days 02:34:57"), Timedelta("-1 days 02:34:57")),
            ("2s", Timedelta("1 days 02:34:56"), Timedelta("-1 days 02:34:56")),
            ("5s", Timedelta("1 days 02:34:55"), Timedelta("-1 days 02:34:55")),
            ("min", Timedelta("1 days 02:35:00"), Timedelta("-1 days 02:35:00")),
            ("12min", Timedelta("1 days 02:36:00"), Timedelta("-1 days 02:36:00")),
            ("h", Timedelta("1 days 03:00:00"), Timedelta("-1 days 03:00:00")),
            ("d", Timedelta("1 days"), Timedelta("-1 days")),
        ],
    )
    def test_round(self, freq, s1, s2):
        t1 = Timedelta("1 days 02:34:56.789123456")
        t2 = Timedelta("-1 days 02:34:56.789123456")
 
        r1 = t1.round(freq)
        assert r1 == s1
        r2 = t2.round(freq)
        assert r2 == s2
 
    def test_round_invalid(self):
        t1 = Timedelta("1 days 02:34:56.789123456")
 
        for freq, msg in [
            ("YE", "<YearEnd: month=12> is a non-fixed frequency"),
            ("ME", "<MonthEnd> is a non-fixed frequency"),
            ("foobar", "Invalid frequency: foobar"),
        ]:
            with pytest.raises(ValueError, match=msg):
                t1.round(freq)
 
    @pytest.mark.skip_ubsan
    def test_round_implementation_bounds(self):
        # See also: analogous test for Timestamp
        # GH#38964
        result = Timedelta.min.ceil("s")
        expected = Timedelta.min + Timedelta(seconds=1) - Timedelta(145224193)
        assert result == expected
 
        result = Timedelta.max.floor("s")
        expected = Timedelta.max - Timedelta(854775807)
        assert result == expected
 
        msg = (
            r"Cannot round -106752 days \+00:12:43.145224193 to freq=s without overflow"
        )
        with pytest.raises(OutOfBoundsTimedelta, match=msg):
            Timedelta.min.floor("s")
        with pytest.raises(OutOfBoundsTimedelta, match=msg):
            Timedelta.min.round("s")
 
        msg = "Cannot round 106751 days 23:47:16.854775807 to freq=s without overflow"
        with pytest.raises(OutOfBoundsTimedelta, match=msg):
            Timedelta.max.ceil("s")
        with pytest.raises(OutOfBoundsTimedelta, match=msg):
            Timedelta.max.round("s")
 
    @pytest.mark.skip_ubsan
    @given(val=st.integers(min_value=iNaT + 1, max_value=lib.i8max))
    @pytest.mark.parametrize(
        "method", [Timedelta.round, Timedelta.floor, Timedelta.ceil]
    )
    def test_round_sanity(self, val, method):
        cls = Timedelta
        err_cls = OutOfBoundsTimedelta
 
        val = np.int64(val)
        td = cls(val)
 
        def checker(ts, nanos, unit):
            # First check that we do raise in cases where we should
            if nanos == 1:
                pass
            else:
                div, mod = divmod(ts._value, nanos)
                diff = int(nanos - mod)
                lb = ts._value - mod
                assert lb <= ts._value  # i.e. no overflows with python ints
                ub = ts._value + diff
                assert ub > ts._value  # i.e. no overflows with python ints
 
                msg = "without overflow"
                if mod == 0:
                    # We should never be raising in this
                    pass
                elif method is cls.ceil:
                    if ub > cls.max._value:
                        with pytest.raises(err_cls, match=msg):
                            method(ts, unit)
                        return
                elif method is cls.floor:
                    if lb < cls.min._value:
                        with pytest.raises(err_cls, match=msg):
                            method(ts, unit)
                        return
                elif mod >= diff:
                    if ub > cls.max._value:
                        with pytest.raises(err_cls, match=msg):
                            method(ts, unit)
                        return
                elif lb < cls.min._value:
                    with pytest.raises(err_cls, match=msg):
                        method(ts, unit)
                    return
 
            res = method(ts, unit)
 
            td = res - ts
            diff = abs(td._value)
            assert diff < nanos
            assert res._value % nanos == 0
 
            if method is cls.round:
                assert diff <= nanos / 2
            elif method is cls.floor:
                assert res <= ts
            elif method is cls.ceil:
                assert res >= ts
 
        nanos = 1
        checker(td, nanos, "ns")
 
        nanos = 1000
        checker(td, nanos, "us")
 
        nanos = 1_000_000
        checker(td, nanos, "ms")
 
        nanos = 1_000_000_000
        checker(td, nanos, "s")
 
        nanos = 60 * 1_000_000_000
        checker(td, nanos, "min")
 
        nanos = 60 * 60 * 1_000_000_000
        checker(td, nanos, "h")
 
        nanos = 24 * 60 * 60 * 1_000_000_000
        checker(td, nanos, "D")
 
    @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"])
    def test_round_non_nano(self, unit):
        td = Timedelta("1 days 02:34:57").as_unit(unit)
 
        res = td.round("min")
        assert res == Timedelta("1 days 02:35:00")
        assert res._creso == td._creso
 
        res = td.floor("min")
        assert res == Timedelta("1 days 02:34:00")
        assert res._creso == td._creso
 
        res = td.ceil("min")
        assert res == Timedelta("1 days 02:35:00")
        assert res._creso == td._creso