From f597e55ba24ef6b1a019e6651b30b5a1f138735a Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 3 Sep 2019 09:10:39 -0700 Subject: [PATCH 01/10] merge upstream --- pandas/_libs/tslibs/__init__.py | 1 + pandas/_libs/tslibs/c_timestamp.pyx | 30 ++++++++++++++----- pandas/core/arrays/datetimelike.py | 3 +- pandas/errors/__init__.py | 10 +------ pandas/tests/arithmetic/test_timedelta64.py | 5 ++-- .../tests/scalar/timestamp/test_arithmetic.py | 7 +++-- pandas/tests/tslibs/test_api.py | 1 + 7 files changed, 34 insertions(+), 23 deletions(-) diff --git a/pandas/_libs/tslibs/__init__.py b/pandas/_libs/tslibs/__init__.py index 67a323782a836..dd538748ff44d 100644 --- a/pandas/_libs/tslibs/__init__.py +++ b/pandas/_libs/tslibs/__init__.py @@ -3,6 +3,7 @@ from .conversion import localize_pydatetime, normalize_date from .nattype import NaT, NaTType, iNaT, is_null_datetimelike from .np_datetime import OutOfBoundsDatetime +from .c_timestamp import NullFrequencyError # Note: import fails if earlier from .period import IncompatibleFrequency, Period from .timedeltas import Timedelta, delta_to_nanoseconds, ints_to_pytimedelta from .timestamps import Timestamp diff --git a/pandas/_libs/tslibs/c_timestamp.pyx b/pandas/_libs/tslibs/c_timestamp.pyx index 41e2ae6b5b59b..db156edd4cd3f 100644 --- a/pandas/_libs/tslibs/c_timestamp.pyx +++ b/pandas/_libs/tslibs/c_timestamp.pyx @@ -42,6 +42,15 @@ from pandas._libs.tslibs.timezones import UTC from pandas._libs.tslibs.tzconversion cimport tz_convert_single +class NullFrequencyError(ValueError): + """ + Error raised when a null `freq` attribute is used in an operation + that needs a non-null frequency, particularly `DatetimeIndex.shift`, + `TimedeltaIndex.shift`, `PeriodIndex.shift`. + """ + pass + + def maybe_integer_op_deprecated(obj): # GH#22535 add/sub of integers and int-arrays is deprecated if obj.freq is not None: @@ -227,8 +236,8 @@ cdef class _Timestamp(datetime): # to be compat with Period return NaT elif self.freq is None: - raise ValueError("Cannot add integral value to Timestamp " - "without freq.") + raise NullFrequencyError( + "Cannot add integral value to Timestamp without freq.") return self.__class__((self.freq * other).apply(self), freq=self.freq) @@ -248,6 +257,7 @@ cdef class _Timestamp(datetime): tz=self.tzinfo, freq=self.freq) if getattr(other, 'normalize', False): # DateOffset + assert False, other result = result.normalize() return result @@ -255,8 +265,9 @@ cdef class _Timestamp(datetime): if other.dtype.kind in ['i', 'u']: maybe_integer_op_deprecated(self) if self.freq is None: - raise ValueError("Cannot add integer-dtype array " - "to Timestamp without freq.") + raise NullFrequencyError( + "Cannot add integer-dtype array " + "to Timestamp without freq.") return self.freq * other + self # index/series like @@ -270,6 +281,8 @@ cdef class _Timestamp(datetime): return result def __sub__(self, other): + + # FIXME: why are we allowing integer through here? if (is_timedelta64_object(other) or is_integer_object(other) or PyDelta_Check(other) or hasattr(other, 'delta')): # `delta` attribute is for offsets.Tick or offsets.Week obj @@ -280,15 +293,16 @@ cdef class _Timestamp(datetime): if other.dtype.kind in ['i', 'u']: maybe_integer_op_deprecated(self) if self.freq is None: - raise ValueError("Cannot subtract integer-dtype array " - "from Timestamp without freq.") + raise NullFrequencyError( + "Cannot subtract integer-dtype array " + "from Timestamp without freq.") return self - self.freq * other typ = getattr(other, '_typ', None) if typ is not None: return NotImplemented - elif other is NaT: + if other is NaT: return NaT # coerce if necessary if we are a Timestamp-like @@ -312,6 +326,8 @@ cdef class _Timestamp(datetime): except (OverflowError, OutOfBoundsDatetime): pass + return NotImplemented + # scalar Timestamp/datetime - Timedelta -> yields a Timestamp (with # same timezone if specified) return datetime.__sub__(self, other) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index bda5f8f4326f1..f18b3e5a2dff8 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -1330,11 +1330,10 @@ def __rsub__(self, other): ) elif is_timedelta64_dtype(self.dtype): if lib.is_integer(other) or is_integer_dtype(other): - # need to subtract before negating, since that flips freq # -self flips self.freq, messing up results return -(self - other) - return (-self) + other + return (-self).__add__(other) return -(self - other) diff --git a/pandas/errors/__init__.py b/pandas/errors/__init__.py index 3177937ac4ba1..a85fc8bfb1414 100644 --- a/pandas/errors/__init__.py +++ b/pandas/errors/__init__.py @@ -4,7 +4,7 @@ Expose public exceptions & warnings """ -from pandas._libs.tslibs import OutOfBoundsDatetime +from pandas._libs.tslibs import NullFrequencyError, OutOfBoundsDatetime class PerformanceWarning(Warning): @@ -157,14 +157,6 @@ class MergeError(ValueError): """ -class NullFrequencyError(ValueError): - """ - Error raised when a null `freq` attribute is used in an operation - that needs a non-null frequency, particularly `DatetimeIndex.shift`, - `TimedeltaIndex.shift`, `PeriodIndex.shift`. - """ - - class AccessorRegistrationWarning(Warning): """Warning for attribute conflicts in accessor registration.""" diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index ee27ce97f269e..36bec253c7bb8 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -242,8 +242,9 @@ def test_subtraction_ops(self): tdi - dti msg = ( - r"descriptor '__sub__' requires a 'datetime\.datetime' object" - " but received a 'Timedelta'" + #r"descriptor '__sub__' requires a 'datetime\.datetime' object" + #" but received a 'Timedelta'|" + r"unsupported operand type\(s\) for -" ) with pytest.raises(TypeError, match=msg): td - dt diff --git a/pandas/tests/scalar/timestamp/test_arithmetic.py b/pandas/tests/scalar/timestamp/test_arithmetic.py index 2ef4fe79eeacf..a9ff6baa1bf92 100644 --- a/pandas/tests/scalar/timestamp/test_arithmetic.py +++ b/pandas/tests/scalar/timestamp/test_arithmetic.py @@ -4,6 +4,7 @@ import pytest from pandas import Timedelta, Timestamp +from pandas.errors import NullFrequencyError import pandas.util.testing as tm from pandas.tseries import offsets @@ -163,12 +164,12 @@ def test_timestamp_add_timedelta64_unit(self, other, expected_difference): ], ) def test_add_int_no_freq_raises(self, ts, other): - with pytest.raises(ValueError, match="without freq"): + with pytest.raises(NullFrequencyError, match="without freq"): ts + other - with pytest.raises(ValueError, match="without freq"): + with pytest.raises(NullFrequencyError, match="without freq"): other + ts - with pytest.raises(ValueError, match="without freq"): + with pytest.raises(NullFrequencyError, match="without freq"): ts - other with pytest.raises(TypeError): other - ts diff --git a/pandas/tests/tslibs/test_api.py b/pandas/tests/tslibs/test_api.py index 47e398dfe3d16..7a8a6d511aa69 100644 --- a/pandas/tests/tslibs/test_api.py +++ b/pandas/tests/tslibs/test_api.py @@ -29,6 +29,7 @@ def test_namespace(): "NaTType", "iNaT", "is_null_datetimelike", + "NullFrequencyError", "OutOfBoundsDatetime", "Period", "IncompatibleFrequency", From 072f0dade86f03e3faa5540c712228822b613c23 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 3 Sep 2019 09:12:31 -0700 Subject: [PATCH 02/10] remove unreachable --- pandas/_libs/tslibs/c_timestamp.pyx | 4 ---- pandas/core/arrays/datetimelike.py | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/pandas/_libs/tslibs/c_timestamp.pyx b/pandas/_libs/tslibs/c_timestamp.pyx index db156edd4cd3f..c153abe76bed9 100644 --- a/pandas/_libs/tslibs/c_timestamp.pyx +++ b/pandas/_libs/tslibs/c_timestamp.pyx @@ -255,10 +255,6 @@ cdef class _Timestamp(datetime): result = self.__class__(self.value + nanos, tz=self.tzinfo, freq=self.freq) - if getattr(other, 'normalize', False): - # DateOffset - assert False, other - result = result.normalize() return result elif is_array(other): diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index f18b3e5a2dff8..e6ddd139fd3ff 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -1330,6 +1330,7 @@ def __rsub__(self, other): ) elif is_timedelta64_dtype(self.dtype): if lib.is_integer(other) or is_integer_dtype(other): + # need to subtract before negating, since that flips freq # -self flips self.freq, messing up results return -(self - other) From bb0384d8657a20d403e4b043d3d819f74923aec9 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 3 Sep 2019 10:48:32 -0700 Subject: [PATCH 03/10] remove unreached --- pandas/_libs/tslibs/c_timestamp.pyx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pandas/_libs/tslibs/c_timestamp.pyx b/pandas/_libs/tslibs/c_timestamp.pyx index c153abe76bed9..4dfb4a574ad12 100644 --- a/pandas/_libs/tslibs/c_timestamp.pyx +++ b/pandas/_libs/tslibs/c_timestamp.pyx @@ -324,10 +324,6 @@ cdef class _Timestamp(datetime): return NotImplemented - # scalar Timestamp/datetime - Timedelta -> yields a Timestamp (with - # same timezone if specified) - return datetime.__sub__(self, other) - cdef int64_t _maybe_convert_value_to_local(self): """Convert UTC i8 value to local i8 value if tz exists""" cdef: From bf128fdd384f1bd23a515a1775c0a3c608c1709e Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 3 Sep 2019 10:49:54 -0700 Subject: [PATCH 04/10] remove comment --- pandas/_libs/tslibs/c_timestamp.pyx | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/_libs/tslibs/c_timestamp.pyx b/pandas/_libs/tslibs/c_timestamp.pyx index 4dfb4a574ad12..0147af9e4b76b 100644 --- a/pandas/_libs/tslibs/c_timestamp.pyx +++ b/pandas/_libs/tslibs/c_timestamp.pyx @@ -278,7 +278,6 @@ cdef class _Timestamp(datetime): def __sub__(self, other): - # FIXME: why are we allowing integer through here? if (is_timedelta64_object(other) or is_integer_object(other) or PyDelta_Check(other) or hasattr(other, 'delta')): # `delta` attribute is for offsets.Tick or offsets.Week obj From 9b829a14a7f5fb69d0694c7e1bed9cdc114513b9 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 3 Sep 2019 10:50:53 -0700 Subject: [PATCH 05/10] revert unnecessary --- pandas/core/arrays/datetimelike.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index e6ddd139fd3ff..bda5f8f4326f1 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -1334,7 +1334,7 @@ def __rsub__(self, other): # -self flips self.freq, messing up results return -(self - other) - return (-self).__add__(other) + return (-self) + other return -(self - other) From b17b036bf6aeb45117a0cbcf096047a4d4880edd Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 3 Sep 2019 10:52:53 -0700 Subject: [PATCH 06/10] remove commented-out --- pandas/tests/arithmetic/test_timedelta64.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index 36bec253c7bb8..d480b26e30fff 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -241,11 +241,7 @@ def test_subtraction_ops(self): with pytest.raises(TypeError, match=msg): tdi - dti - msg = ( - #r"descriptor '__sub__' requires a 'datetime\.datetime' object" - #" but received a 'Timedelta'|" - r"unsupported operand type\(s\) for -" - ) + msg = r"unsupported operand type\(s\) for -" with pytest.raises(TypeError, match=msg): td - dt From 720eeb0ef55ef0ee85a196757921d9fdc4980732 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 3 Sep 2019 12:05:32 -0700 Subject: [PATCH 07/10] black --- pandas/_libs/tslibs/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/__init__.py b/pandas/_libs/tslibs/__init__.py index dd538748ff44d..b7214be941301 100644 --- a/pandas/_libs/tslibs/__init__.py +++ b/pandas/_libs/tslibs/__init__.py @@ -3,7 +3,7 @@ from .conversion import localize_pydatetime, normalize_date from .nattype import NaT, NaTType, iNaT, is_null_datetimelike from .np_datetime import OutOfBoundsDatetime -from .c_timestamp import NullFrequencyError # Note: import fails if earlier +from .c_timestamp import NullFrequencyError # Note: import fails if earlier from .period import IncompatibleFrequency, Period from .timedeltas import Timedelta, delta_to_nanoseconds, ints_to_pytimedelta from .timestamps import Timestamp From 72990a0ae3fc090776aa494b3cd1f6074fd11147 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 3 Sep 2019 12:07:25 -0700 Subject: [PATCH 08/10] isort fixup --- pandas/_libs/tslibs/__init__.py | 4 +++- pandas/tests/scalar/timestamp/test_arithmetic.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pandas/_libs/tslibs/__init__.py b/pandas/_libs/tslibs/__init__.py index b7214be941301..8d3b00e4a44b9 100644 --- a/pandas/_libs/tslibs/__init__.py +++ b/pandas/_libs/tslibs/__init__.py @@ -3,8 +3,10 @@ from .conversion import localize_pydatetime, normalize_date from .nattype import NaT, NaTType, iNaT, is_null_datetimelike from .np_datetime import OutOfBoundsDatetime -from .c_timestamp import NullFrequencyError # Note: import fails if earlier from .period import IncompatibleFrequency, Period from .timedeltas import Timedelta, delta_to_nanoseconds, ints_to_pytimedelta from .timestamps import Timestamp from .tzconversion import tz_convert_single + +# import fails if we do this before np_datetime +from .c_timestamp import NullFrequencyError # isort:skip diff --git a/pandas/tests/scalar/timestamp/test_arithmetic.py b/pandas/tests/scalar/timestamp/test_arithmetic.py index a9ff6baa1bf92..d298433730afc 100644 --- a/pandas/tests/scalar/timestamp/test_arithmetic.py +++ b/pandas/tests/scalar/timestamp/test_arithmetic.py @@ -3,8 +3,9 @@ import numpy as np import pytest -from pandas import Timedelta, Timestamp from pandas.errors import NullFrequencyError + +from pandas import Timedelta, Timestamp import pandas.util.testing as tm from pandas.tseries import offsets From 08dc007c08de722ebee3441c3834607a4daad40d Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 3 Sep 2019 14:22:53 -0700 Subject: [PATCH 09/10] whatsnew --- doc/source/whatsnew/v0.25.2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.25.2.rst b/doc/source/whatsnew/v0.25.2.rst index 1cdf213d81a74..d12608983c140 100644 --- a/doc/source/whatsnew/v0.25.2.rst +++ b/doc/source/whatsnew/v0.25.2.rst @@ -18,7 +18,7 @@ Categorical Datetimelike ^^^^^^^^^^^^ - +- Addition and subtraction of integer or integer-dtype arrays with :class:`Timestamp` will now raise ``NullFrequencyError`` instead of ``ValueError`` (:issue:`28268`) - - - From f0ab301093bfc82e3f9ed840d4e8e6cfae5ce336 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Wed, 4 Sep 2019 08:31:54 -0700 Subject: [PATCH 10/10] move note to 1.0 file --- doc/source/whatsnew/v0.25.2.rst | 2 +- doc/source/whatsnew/v1.0.0.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v0.25.2.rst b/doc/source/whatsnew/v0.25.2.rst index d12608983c140..1cdf213d81a74 100644 --- a/doc/source/whatsnew/v0.25.2.rst +++ b/doc/source/whatsnew/v0.25.2.rst @@ -18,7 +18,7 @@ Categorical Datetimelike ^^^^^^^^^^^^ -- Addition and subtraction of integer or integer-dtype arrays with :class:`Timestamp` will now raise ``NullFrequencyError`` instead of ``ValueError`` (:issue:`28268`) + - - - diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 0d2b81eca6789..a35139cbfd5ef 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -97,9 +97,9 @@ Datetimelike - Bug in :meth:`Series.__setitem__` incorrectly casting ``np.timedelta64("NaT")`` to ``np.datetime64("NaT")`` when inserting into a :class:`Series` with datetime64 dtype (:issue:`27311`) - Bug in :meth:`Series.dt` property lookups when the underlying data is read-only (:issue:`27529`) - Bug in ``HDFStore.__getitem__`` incorrectly reading tz attribute created in Python 2 (:issue:`26443`) +- Addition and subtraction of integer or integer-dtype arrays with :class:`Timestamp` will now raise ``NullFrequencyError`` instead of ``ValueError`` (:issue:`28268`) - - Timedelta ^^^^^^^^^