Skip to content

Commit 36c2a48

Browse files
mcgeestocksim-vinicius
authored and
im-vinicius
committed
BUG: Adds missing raises for numpy.timedelta64[M/Y] in pandas.Timedelta (pandas-dev#53421)
* adds raise for numpy Y and M conversion in Timedelta * fix failing tests * update raise lang * alternative approach * add missing periods * switch to cimport * add cython directive * switch to inline cython directive * remove unsupported td operation * adds whatnew update * remove whitespace * adds test
1 parent 00cad1a commit 36c2a48

File tree

8 files changed

+24
-9
lines changed

8 files changed

+24
-9
lines changed

doc/source/user_guide/timedeltas.rst

-3
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,6 @@ an alternative is to divide by another timedelta object. Note that division by t
259259
# to days
260260
td / np.timedelta64(1, "D")
261261
262-
# to months (these are constant months)
263-
td / np.timedelta64(1, "M")
264-
265262
Dividing or multiplying a ``timedelta64[ns]`` Series by an integer or integer Series
266263
yields another ``timedelta64[ns]`` dtypes Series.
267264

doc/source/whatsnew/v2.1.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ Timedelta
368368
^^^^^^^^^
369369
- :meth:`TimedeltaIndex.map` with ``na_action="ignore"`` now works as expected (:issue:`51644`)
370370
- Bug in :class:`TimedeltaIndex` division or multiplication leading to ``.freq`` of "0 Days" instead of ``None`` (:issue:`51575`)
371+
- Bug in :class:`Timedelta` with Numpy timedelta64 objects not properly raising ``ValueError`` (:issue:`52806`)
371372
- Bug in :meth:`Timedelta.round` with values close to the implementation bounds returning incorrect results instead of raising ``OutOfBoundsTimedelta`` (:issue:`51494`)
372373
- Bug in :meth:`arrays.TimedeltaArray.map` and :meth:`TimedeltaIndex.map`, where the supplied callable operated array-wise instead of element-wise (:issue:`51977`)
373374
-

pandas/_libs/tslibs/dtypes.pxd

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ cdef NPY_DATETIMEUNIT freq_group_code_to_npy_unit(int freq) noexcept nogil
99
cpdef int64_t periods_per_day(NPY_DATETIMEUNIT reso=*) except? -1
1010
cpdef int64_t periods_per_second(NPY_DATETIMEUNIT reso) except? -1
1111
cpdef NPY_DATETIMEUNIT get_supported_reso(NPY_DATETIMEUNIT reso)
12+
cpdef bint is_supported_unit(NPY_DATETIMEUNIT reso)
1213

1314
cdef dict attrname_to_abbrevs
1415
cdef dict npy_unit_to_attrname

pandas/_libs/tslibs/dtypes.pyx

+1-1
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ cpdef NPY_DATETIMEUNIT get_supported_reso(NPY_DATETIMEUNIT reso):
321321
return reso
322322

323323

324-
def is_supported_unit(NPY_DATETIMEUNIT reso):
324+
cpdef bint is_supported_unit(NPY_DATETIMEUNIT reso):
325325
return (
326326
reso == NPY_DATETIMEUNIT.NPY_FR_ns
327327
or reso == NPY_DATETIMEUNIT.NPY_FR_us

pandas/_libs/tslibs/timedeltas.pyx

+11-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ from pandas._libs.tslibs.conversion cimport (
4242
)
4343
from pandas._libs.tslibs.dtypes cimport (
4444
get_supported_reso,
45+
is_supported_unit,
4546
npy_unit_to_abbrev,
4647
)
4748
from pandas._libs.tslibs.nattype cimport (
@@ -151,10 +152,10 @@ cdef dict timedelta_abbrevs = {
151152

152153
_no_input = object()
153154

154-
155155
# ----------------------------------------------------------------------
156156
# API
157157

158+
158159
@cython.boundscheck(False)
159160
@cython.wraparound(False)
160161
def ints_to_pytimedelta(ndarray m8values, box=False):
@@ -1789,7 +1790,6 @@ class Timedelta(_Timedelta):
17891790
+ int(kwargs.get("milliseconds", 0) * 1_000_000)
17901791
+ seconds
17911792
)
1792-
17931793
if unit in {"Y", "y", "M"}:
17941794
raise ValueError(
17951795
"Units 'M', 'Y', and 'y' are no longer supported, as they do not "
@@ -1836,6 +1836,15 @@ class Timedelta(_Timedelta):
18361836
return NaT
18371837

18381838
reso = get_datetime64_unit(value)
1839+
if not (is_supported_unit(reso) or
1840+
reso in [NPY_DATETIMEUNIT.NPY_FR_m,
1841+
NPY_DATETIMEUNIT.NPY_FR_h,
1842+
NPY_DATETIMEUNIT.NPY_FR_D,
1843+
NPY_DATETIMEUNIT.NPY_FR_W,
1844+
NPY_DATETIMEUNIT.NPY_FR_GENERIC]):
1845+
err = npy_unit_to_abbrev(reso)
1846+
raise ValueError(f" cannot construct a Timedelta from a unit {err}")
1847+
18391848
new_reso = get_supported_reso(reso)
18401849
if reso != NPY_DATETIMEUNIT.NPY_FR_GENERIC:
18411850
try:

pandas/tests/libs/test_lib.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def test_fast_multiget_timedelta_resos(self):
5959
tm.assert_numpy_array_equal(result, expected)
6060

6161
# case that can't be cast to td64ns
62-
td = Timedelta(np.timedelta64(400, "Y"))
62+
td = Timedelta(np.timedelta64(146000, "D"))
6363
assert hash(td) == hash(td.as_unit("ms"))
6464
assert hash(td) == hash(td.as_unit("us"))
6565
mapping1 = {td: 1}

pandas/tests/scalar/timedelta/test_constructors.py

-2
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,6 @@ def test_overflow_on_construction():
289289
@pytest.mark.parametrize(
290290
"val, unit",
291291
[
292-
(3508, "M"),
293292
(15251, "W"), # 1
294293
(106752, "D"), # change from previous:
295294
(2562048, "h"), # 0 hours
@@ -333,7 +332,6 @@ def test_construction_out_of_bounds_td64ns(val, unit):
333332
@pytest.mark.parametrize(
334333
"val, unit",
335334
[
336-
(3508 * 10**9, "M"),
337335
(15251 * 10**9, "W"),
338336
(106752 * 10**9, "D"),
339337
(2562048 * 10**9, "h"),

pandas/tests/tslibs/test_timedeltas.py

+9
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ def test_delta_to_nanoseconds_td64_MY_raises():
7272
delta_to_nanoseconds(td)
7373

7474

75+
@pytest.mark.parametrize("unit", ["Y", "M"])
76+
def test_unsupported_td64_unit_raises(unit):
77+
# GH 52806
78+
with pytest.raises(
79+
ValueError, match=f"cannot construct a Timedelta from a unit {unit}"
80+
):
81+
Timedelta(np.timedelta64(1, unit))
82+
83+
7584
def test_huge_nanoseconds_overflow():
7685
# GH 32402
7786
assert delta_to_nanoseconds(Timedelta(1e10)) == 1e10

0 commit comments

Comments
 (0)