Skip to content

Commit 1a9937d

Browse files
TomAugspurgerjreback
authored andcommitted
Warn on ndarray[int] // timedelta (#21036)
1 parent 3da49cb commit 1a9937d

File tree

5 files changed

+43
-6
lines changed

5 files changed

+43
-6
lines changed

doc/source/timeseries.rst

+4-3
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ Pass ``errors='coerce'`` to convert unparseable data to ``NaT`` (not a time):
257257
Epoch Timestamps
258258
~~~~~~~~~~~~~~~~
259259

260-
pandas supports converting integer or float epoch times to ``Timestamp`` and
260+
pandas supports converting integer or float epoch times to ``Timestamp`` and
261261
``DatetimeIndex``. The default unit is nanoseconds, since that is how ``Timestamp``
262262
objects are stored internally. However, epochs are often stored in another ``unit``
263263
which can be specified. These are computed from the starting point specified by the
@@ -304,11 +304,12 @@ To invert the operation from above, namely, to convert from a ``Timestamp`` to a
304304
stamps = pd.date_range('2012-10-08 18:15:05', periods=4, freq='D')
305305
stamps
306306
307-
We convert the ``DatetimeIndex`` to an ``int64`` array, then divide by the conversion unit.
307+
We subtract the epoch (midnight at January 1, 1970 UTC) and then floor divide by the
308+
"unit" (1 second).
308309

309310
.. ipython:: python
310311
311-
stamps.view('int64') // pd.Timedelta(1, unit='s')
312+
(stamps - pd.Timestamp("1970-01-01")) // pd.Timedelta('1s')
312313
313314
.. _timeseries.origin:
314315

doc/source/whatsnew/v0.23.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,7 @@ Deprecations
10041004
of the ``Series`` and ``Index`` classes have been deprecated and will be
10051005
removed in a future version (:issue:`20419`).
10061006
- ``DatetimeIndex.offset`` is deprecated. Use ``DatetimeIndex.freq`` instead (:issue:`20716`)
1007+
- Floor division between an integer ndarray and a :class:`Timedelta` is deprecated. Divide by :attr:`Timedelta.value` instead (:issue:`19761`)
10071008
- Setting ``PeriodIndex.freq`` (which was not guaranteed to work correctly) is deprecated. Use :meth:`PeriodIndex.asfreq` instead (:issue:`20678`)
10081009
- ``Index.get_duplicates()`` is deprecated and will be removed in a future version (:issue:`20239`)
10091010
- The previous default behavior of negative indices in ``Categorical.take`` is deprecated. In a future version it will change from meaning missing values to meaning positional indices from the right. The future behavior is consistent with :meth:`Series.take` (:issue:`20664`).

pandas/_libs/tslibs/timedeltas.pyx

+21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# -*- coding: utf-8 -*-
22
# cython: profile=False
33
import collections
4+
import textwrap
5+
import warnings
46

57
import sys
68
cdef bint PY3 = (sys.version_info[0] >= 3)
@@ -1188,6 +1190,15 @@ class Timedelta(_Timedelta):
11881190
if other.dtype.kind == 'm':
11891191
# also timedelta-like
11901192
return _broadcast_floordiv_td64(self.value, other, _rfloordiv)
1193+
elif other.dtype.kind == 'i':
1194+
# Backwards compatibility
1195+
# GH-19761
1196+
msg = textwrap.dedent("""\
1197+
Floor division between integer array and Timedelta is
1198+
deprecated. Use 'array // timedelta.value' instead.
1199+
""")
1200+
warnings.warn(msg, FutureWarning)
1201+
return other // self.value
11911202
raise TypeError('Invalid dtype {dtype} for '
11921203
'{op}'.format(dtype=other.dtype,
11931204
op='__floordiv__'))
@@ -1210,6 +1221,11 @@ class Timedelta(_Timedelta):
12101221

12111222
def __rmod__(self, other):
12121223
# Naive implementation, room for optimization
1224+
if hasattr(other, 'dtype') and other.dtype.kind == 'i':
1225+
# TODO: Remove this check with backwards-compat shim
1226+
# for integer / Timedelta is removed.
1227+
raise TypeError("Invalid type {dtype} for "
1228+
"{op}".format(dtype=other.dtype, op='__mod__'))
12131229
return self.__rdivmod__(other)[1]
12141230

12151231
def __divmod__(self, other):
@@ -1219,6 +1235,11 @@ class Timedelta(_Timedelta):
12191235

12201236
def __rdivmod__(self, other):
12211237
# Naive implementation, room for optimization
1238+
if hasattr(other, 'dtype') and other.dtype.kind == 'i':
1239+
# TODO: Remove this check with backwards-compat shim
1240+
# for integer / Timedelta is removed.
1241+
raise TypeError("Invalid type {dtype} for "
1242+
"{op}".format(dtype=other.dtype, op='__mod__'))
12221243
div = other // self
12231244
return div, other - div * self
12241245

pandas/tests/scalar/timedelta/test_arithmetic.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -403,10 +403,11 @@ def test_td_rfloordiv_numeric_scalar(self):
403403

404404
with pytest.raises(TypeError):
405405
td.__rfloordiv__(np.float64(2.0))
406-
with pytest.raises(TypeError):
407-
td.__rfloordiv__(np.int32(2.0))
408406
with pytest.raises(TypeError):
409407
td.__rfloordiv__(np.uint8(9))
408+
with tm.assert_produces_warning(FutureWarning):
409+
# GH-19761: Change to TypeError.
410+
td.__rfloordiv__(np.int32(2.0))
410411

411412
def test_td_rfloordiv_timedeltalike_array(self):
412413
# GH#18846
@@ -432,7 +433,8 @@ def test_td_rfloordiv_numeric_series(self):
432433
ser = pd.Series([1], dtype=np.int64)
433434
res = td.__rfloordiv__(ser)
434435
assert res is NotImplemented
435-
with pytest.raises(TypeError):
436+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
437+
# TODO: GH-19761. Change to TypeError.
436438
ser // td
437439

438440
def test_mod_timedeltalike(self):

pandas/tests/scalar/timedelta/test_timedelta.py

+12
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ def test_arithmetic_overflow(self):
2121
with pytest.raises(OverflowError):
2222
pd.Timestamp('1700-01-01') + timedelta(days=13 * 19999)
2323

24+
def test_array_timedelta_floordiv(self):
25+
# https://github.com/pandas-dev/pandas/issues/19761
26+
ints = pd.date_range('2012-10-08', periods=4, freq='D').view('i8')
27+
msg = r"Use 'array // timedelta.value'"
28+
with tm.assert_produces_warning(FutureWarning) as m:
29+
result = ints // pd.Timedelta(1, unit='s')
30+
31+
assert msg in str(m[0].message)
32+
expected = np.array([1349654400, 1349740800, 1349827200, 1349913600],
33+
dtype='i8')
34+
tm.assert_numpy_array_equal(result, expected)
35+
2436
def test_ops_error_str(self):
2537
# GH 13624
2638
td = Timedelta('1 day')

0 commit comments

Comments
 (0)