Skip to content

Commit db34bae

Browse files
authored
ENH: don't silently ignore dtype in NaT/Timestamp/Timedelta to_numpy (#44460)
1 parent 8248079 commit db34bae

File tree

8 files changed

+69
-6
lines changed

8 files changed

+69
-6
lines changed

doc/source/whatsnew/v1.4.0.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,8 @@ Other enhancements
210210
- :meth:`read_excel` now accepts a ``decimal`` argument that allow the user to specify the decimal point when parsing string columns to numeric (:issue:`14403`)
211211
- :meth:`.GroupBy.mean` now supports `Numba <http://numba.pydata.org/>`_ execution with the ``engine`` keyword (:issue:`43731`)
212212
- :meth:`Timestamp.isoformat`, now handles the ``timespec`` argument from the base :class:``datetime`` class (:issue:`26131`)
213+
- :meth:`NaT.to_numpy` ``dtype`` argument is now respected, so ``np.timedelta64`` can be returned (:issue:`44460`)
214+
-
213215

214216
.. ---------------------------------------------------------------------------
215217

pandas/_libs/tslibs/nattype.pyi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ class NaTType(datetime):
1818
value: np.int64
1919
def asm8(self) -> np.datetime64: ...
2020
def to_datetime64(self) -> np.datetime64: ...
21-
def to_numpy(self, dtype=..., copy: bool = ...) -> np.datetime64: ...
21+
def to_numpy(
22+
self, dtype=..., copy: bool = ...
23+
) -> np.datetime64 | np.timedelta64: ...
2224
@property
2325
def is_leap_year(self) -> bool: ...
2426
@property

pandas/_libs/tslibs/nattype.pyx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,19 +258,20 @@ cdef class _NaT(datetime):
258258
"""
259259
return np.datetime64('NaT', "ns")
260260

261-
def to_numpy(self, dtype=None, copy=False) -> np.datetime64:
261+
def to_numpy(self, dtype=None, copy=False) -> np.datetime64 | np.timedelta64:
262262
"""
263-
Convert the Timestamp to a NumPy datetime64.
263+
Convert the Timestamp to a NumPy datetime64 or timedelta64.
264264

265265
.. versionadded:: 0.25.0
266266

267-
This is an alias method for `Timestamp.to_datetime64()`. The dtype and
268-
copy parameters are available here only for compatibility. Their values
267+
With the default 'dtype', this is an alias method for `NaT.to_datetime64()`.
268+
269+
The copy parameter is available here only for compatibility. Its value
269270
will not affect the return value.
270271

271272
Returns
272273
-------
273-
numpy.datetime64
274+
numpy.datetime64 or numpy.timedelta64
274275

275276
See Also
276277
--------
@@ -286,7 +287,22 @@ cdef class _NaT(datetime):
286287

287288
>>> pd.NaT.to_numpy()
288289
numpy.datetime64('NaT')
290+
291+
>>> pd.NaT.to_numpy("m8[ns]")
292+
numpy.timedelta64('NaT','ns')
289293
"""
294+
if dtype is not None:
295+
# GH#44460
296+
dtype = np.dtype(dtype)
297+
if dtype.kind == "M":
298+
return np.datetime64("NaT").astype(dtype)
299+
elif dtype.kind == "m":
300+
return np.timedelta64("NaT").astype(dtype)
301+
else:
302+
raise ValueError(
303+
"NaT.to_numpy dtype must be a datetime64 dtype, timedelta64 "
304+
"dtype, or None."
305+
)
290306
return self.to_datetime64()
291307

292308
def __repr__(self) -> str:

pandas/_libs/tslibs/timedeltas.pyx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,10 @@ cdef class _Timedelta(timedelta):
929929
--------
930930
Series.to_numpy : Similar method for Series.
931931
"""
932+
if dtype is not None or copy is not False:
933+
raise ValueError(
934+
"Timedelta.to_numpy dtype and copy arguments are ignored"
935+
)
932936
return self.to_timedelta64()
933937

934938
def view(self, dtype):

pandas/_libs/tslibs/timestamps.pyx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,10 @@ cdef class _Timestamp(ABCTimestamp):
934934
>>> pd.NaT.to_numpy()
935935
numpy.datetime64('NaT')
936936
"""
937+
if dtype is not None or copy is not False:
938+
raise ValueError(
939+
"Timestamp.to_numpy dtype and copy arguments are ignored."
940+
)
937941
return self.to_datetime64()
938942

939943
def to_period(self, freq=None):

pandas/tests/scalar/test_nat.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,11 @@ def test_nat_doc_strings(compare):
330330
if klass == Timestamp and method == "isoformat":
331331
return
332332

333+
if method == "to_numpy":
334+
# GH#44460 can return either dt64 or td64 depending on dtype,
335+
# different docstring is intentional
336+
return
337+
333338
nat_doc = getattr(NaT, method).__doc__
334339
assert klass_doc == nat_doc
335340

@@ -511,6 +516,22 @@ def test_to_numpy_alias():
511516

512517
assert isna(expected) and isna(result)
513518

519+
# GH#44460
520+
result = NaT.to_numpy("M8[s]")
521+
assert isinstance(result, np.datetime64)
522+
assert result.dtype == "M8[s]"
523+
524+
result = NaT.to_numpy("m8[ns]")
525+
assert isinstance(result, np.timedelta64)
526+
assert result.dtype == "m8[ns]"
527+
528+
result = NaT.to_numpy("m8[s]")
529+
assert isinstance(result, np.timedelta64)
530+
assert result.dtype == "m8[s]"
531+
532+
with pytest.raises(ValueError, match="NaT.to_numpy dtype must be a "):
533+
NaT.to_numpy(np.int64)
534+
514535

515536
@pytest.mark.parametrize(
516537
"other",

pandas/tests/scalar/timedelta/test_timedelta.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,13 @@ def test_to_numpy_alias(self):
317317
td = Timedelta("10m7s")
318318
assert td.to_timedelta64() == td.to_numpy()
319319

320+
# GH#44460
321+
msg = "dtype and copy arguments are ignored"
322+
with pytest.raises(ValueError, match=msg):
323+
td.to_numpy("m8[s]")
324+
with pytest.raises(ValueError, match=msg):
325+
td.to_numpy(copy=True)
326+
320327
@pytest.mark.parametrize(
321328
"freq,s1,s2",
322329
[

pandas/tests/scalar/timestamp/test_timestamp.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,13 @@ def test_to_numpy_alias(self):
619619
ts = Timestamp(datetime.now())
620620
assert ts.to_datetime64() == ts.to_numpy()
621621

622+
# GH#44460
623+
msg = "dtype and copy arguments are ignored"
624+
with pytest.raises(ValueError, match=msg):
625+
ts.to_numpy("M8[s]")
626+
with pytest.raises(ValueError, match=msg):
627+
ts.to_numpy(copy=True)
628+
622629

623630
class SubDatetime(datetime):
624631
pass

0 commit comments

Comments
 (0)