From bf773e00b942d3783e7508feabe1f33b059dd7e3 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 17 Jun 2019 20:08:00 -0700 Subject: [PATCH 1/6] BUG: Fix timedelta64+Timestamp, closes #24775 --- doc/source/whatsnew/v0.25.0.rst | 1 + pandas/_libs/tslibs/c_timestamp.pyx | 3 +++ pandas/tests/scalar/timestamp/test_arithmetic.py | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 2b1a61186dca6..7bc1ca9b25914 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -567,6 +567,7 @@ Datetimelike - Bug in :meth:`isin` for datetimelike indexes; :class:`DatetimeIndex`, :class:`TimedeltaIndex` and :class:`PeriodIndex` where the ``levels`` parameter was ignored. (:issue:`26675`) - Bug in :func:`to_datetime` which raises ``TypeError`` for ``format='%Y%m%d'`` when called for invalid integer dates with length >= 6 digits with ``errors='ignore'`` - Bug when comparing a :class:`PeriodIndex` against a zero-dimensional numpy array (:issue:`26689`) +- Bug where adding :class:`Timestamp` to a ``np.timedelta64`` object would raise instead of returning a :class:`Timestamp` (:issue:24775) Timedelta ^^^^^^^^^ diff --git a/pandas/_libs/tslibs/c_timestamp.pyx b/pandas/_libs/tslibs/c_timestamp.pyx index 6bf6b6dcea8dd..346dce85451fe 100644 --- a/pandas/_libs/tslibs/c_timestamp.pyx +++ b/pandas/_libs/tslibs/c_timestamp.pyx @@ -55,6 +55,9 @@ def maybe_integer_op_deprecated(obj): cdef class _Timestamp(datetime): + # higher than np.ndarray and np.matrix + __array_priority__ = 100 + def __hash__(_Timestamp self): if self.nanosecond: return hash(self.value) diff --git a/pandas/tests/scalar/timestamp/test_arithmetic.py b/pandas/tests/scalar/timestamp/test_arithmetic.py index 21e1dccaefc4b..4bcb93860031e 100644 --- a/pandas/tests/scalar/timestamp/test_arithmetic.py +++ b/pandas/tests/scalar/timestamp/test_arithmetic.py @@ -112,3 +112,9 @@ def test_addition_subtraction_preserve_frequency(self): td64 = np.timedelta64(1, 'D') assert (ts + td64).freq == original_freq assert (ts - td64).freq == original_freq + + def test_radd_timedelta64(self): + # GH#24775 timedelta64+Timestamp should not raise + ts = Timestamp.now() + td = np.timedelta64(3, 'h') + assert td + ts == ts + td From d5a4eef285fe5b1b074d510dde49b424e25bdf66 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 18 Jun 2019 07:11:48 -0700 Subject: [PATCH 2/6] fix missing backticks --- doc/source/whatsnew/v0.25.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 7bc1ca9b25914..2c8c03f78ea5a 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -567,7 +567,7 @@ Datetimelike - Bug in :meth:`isin` for datetimelike indexes; :class:`DatetimeIndex`, :class:`TimedeltaIndex` and :class:`PeriodIndex` where the ``levels`` parameter was ignored. (:issue:`26675`) - Bug in :func:`to_datetime` which raises ``TypeError`` for ``format='%Y%m%d'`` when called for invalid integer dates with length >= 6 digits with ``errors='ignore'`` - Bug when comparing a :class:`PeriodIndex` against a zero-dimensional numpy array (:issue:`26689`) -- Bug where adding :class:`Timestamp` to a ``np.timedelta64`` object would raise instead of returning a :class:`Timestamp` (:issue:24775) +- Bug where adding :class:`Timestamp` to a ``np.timedelta64`` object would raise instead of returning a :class:`Timestamp` (:issue:`24775`) Timedelta ^^^^^^^^^ From 96664163e4b5bdb714f2ed78534c5fd6859cd0f0 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 18 Jun 2019 09:38:27 -0700 Subject: [PATCH 3/6] fix comparison bug --- doc/source/whatsnew/v0.25.0.rst | 1 + pandas/_libs/tslibs/c_timestamp.pyx | 9 +++++++++ pandas/tests/scalar/timestamp/test_comparisons.py | 12 ++++++++++++ 3 files changed, 22 insertions(+) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 2c8c03f78ea5a..f08d3904617a0 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -568,6 +568,7 @@ Datetimelike - Bug in :func:`to_datetime` which raises ``TypeError`` for ``format='%Y%m%d'`` when called for invalid integer dates with length >= 6 digits with ``errors='ignore'`` - Bug when comparing a :class:`PeriodIndex` against a zero-dimensional numpy array (:issue:`26689`) - Bug where adding :class:`Timestamp` to a ``np.timedelta64`` object would raise instead of returning a :class:`Timestamp` (:issue:`24775`) +- Bug where comparing a zero-dimensional numpy array containing a ``np.datetime64`` object to a :class:`Timestamp` would incorrect raise ``TypeError`` (:issue:`26916`) Timedelta ^^^^^^^^^ diff --git a/pandas/_libs/tslibs/c_timestamp.pyx b/pandas/_libs/tslibs/c_timestamp.pyx index 346dce85451fe..f9d1a906207fe 100644 --- a/pandas/_libs/tslibs/c_timestamp.pyx +++ b/pandas/_libs/tslibs/c_timestamp.pyx @@ -88,6 +88,15 @@ cdef class _Timestamp(datetime): if ndim == 0: if is_datetime64_object(other): other = self.__class__(other) + elif is_array(other): + # zero-dim array, occurs if try comparison with + # datetime64 scalar on the left hand side + # Unfortunately, for datetime64 values, other.item() + # incorrectly returns an integer, so we need to use + # the numpy C api to extract it. + other = cnp.PyArray_ToScalar(cnp.PyArray_DATA(other), + other) + other = self.__class__(other) else: return NotImplemented elif is_array(other): diff --git a/pandas/tests/scalar/timestamp/test_comparisons.py b/pandas/tests/scalar/timestamp/test_comparisons.py index 763cfc23ea832..b7782e685114a 100644 --- a/pandas/tests/scalar/timestamp/test_comparisons.py +++ b/pandas/tests/scalar/timestamp/test_comparisons.py @@ -156,6 +156,18 @@ def test_timestamp_compare_with_early_datetime(self): assert stamp < datetime(2700, 1, 1) assert stamp <= datetime(2700, 1, 1) + def test_compare_zerodim_array(self): + # GH#26916 + ts = Timestamp.now() + td64 = np.datetime64('2016-01-01', 'ns') + arr = np.array(td64) + assert arr.ndim == 0 + + result = arr < ts + assert result is True + result = arr > ts + assert result is False + def test_rich_comparison_with_unsupported_type(): # Comparisons with unsupported objects should return NotImplemented From 3c48d57920f1d64334077d72a9191ab59fc623e6 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 18 Jun 2019 18:10:49 -0700 Subject: [PATCH 4/6] Collect Timestamp arith tests, parametrize --- .../tests/scalar/timestamp/test_arithmetic.py | 12 +++++++++ .../scalar/timestamp/test_comparisons.py | 4 +-- .../tests/scalar/timestamp/test_timestamp.py | 25 ------------------- 3 files changed, 14 insertions(+), 27 deletions(-) diff --git a/pandas/tests/scalar/timestamp/test_arithmetic.py b/pandas/tests/scalar/timestamp/test_arithmetic.py index 4bcb93860031e..4fb1f63a2bad2 100644 --- a/pandas/tests/scalar/timestamp/test_arithmetic.py +++ b/pandas/tests/scalar/timestamp/test_arithmetic.py @@ -118,3 +118,15 @@ def test_radd_timedelta64(self): ts = Timestamp.now() td = np.timedelta64(3, 'h') assert td + ts == ts + td + + @pytest.mark.parametrize('other,exdiff', [ + (np.timedelta64(-123, 'ns'), -123), + (np.timedelta64(1234567898, 'ns'), 1234567898), + (np.timedelta64(-123, 'us'), -123000), + (np.timedelta64(-123, 'ms'), -123000000) + ]) + def test_timestamp_add_timedelta64_unit(self, other, exdiff): + ts = Timestamp(datetime.utcnow()) + result = ts + other + valdiff = result.value - ts.value + assert valdiff == exdiff diff --git a/pandas/tests/scalar/timestamp/test_comparisons.py b/pandas/tests/scalar/timestamp/test_comparisons.py index b7782e685114a..b572b4607108c 100644 --- a/pandas/tests/scalar/timestamp/test_comparisons.py +++ b/pandas/tests/scalar/timestamp/test_comparisons.py @@ -159,8 +159,8 @@ def test_timestamp_compare_with_early_datetime(self): def test_compare_zerodim_array(self): # GH#26916 ts = Timestamp.now() - td64 = np.datetime64('2016-01-01', 'ns') - arr = np.array(td64) + dt64 = np.datetime64('2016-01-01', 'ns') + arr = np.array(dt64) assert arr.ndim == 0 result = arr < ts diff --git a/pandas/tests/scalar/timestamp/test_timestamp.py b/pandas/tests/scalar/timestamp/test_timestamp.py index 773b4e6f21a19..03adcd4712096 100644 --- a/pandas/tests/scalar/timestamp/test_timestamp.py +++ b/pandas/tests/scalar/timestamp/test_timestamp.py @@ -789,31 +789,6 @@ def test_tz_conversion_freq(self, tz_naive_fixture): class TestTimestampNsOperations: - def setup_method(self, method): - self.timestamp = Timestamp(datetime.utcnow()) - - def assert_ns_timedelta(self, modified_timestamp, expected_value): - value = self.timestamp.value - modified_value = modified_timestamp.value - - assert modified_value - value == expected_value - - def test_timedelta_ns_arithmetic(self): - self.assert_ns_timedelta(self.timestamp + np.timedelta64(-123, 'ns'), - -123) - - def test_timedelta_ns_based_arithmetic(self): - self.assert_ns_timedelta(self.timestamp + np.timedelta64( - 1234567898, 'ns'), 1234567898) - - def test_timedelta_us_arithmetic(self): - self.assert_ns_timedelta(self.timestamp + np.timedelta64(-123, 'us'), - -123000) - - def test_timedelta_ms_arithmetic(self): - time = self.timestamp + np.timedelta64(-123, 'ms') - self.assert_ns_timedelta(time, -123000000) - def test_nanosecond_string_parsing(self): ts = Timestamp('2013-05-01 07:15:45.123456789') # GH 7878 From d486dcda52f3721a604941d9ac5ae76566728348 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Thu, 20 Jun 2019 18:49:08 -0700 Subject: [PATCH 5/6] rename exdiff-->expected_difference --- pandas/tests/scalar/timestamp/test_arithmetic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/tests/scalar/timestamp/test_arithmetic.py b/pandas/tests/scalar/timestamp/test_arithmetic.py index 4fb1f63a2bad2..8521fddf788e5 100644 --- a/pandas/tests/scalar/timestamp/test_arithmetic.py +++ b/pandas/tests/scalar/timestamp/test_arithmetic.py @@ -119,14 +119,14 @@ def test_radd_timedelta64(self): td = np.timedelta64(3, 'h') assert td + ts == ts + td - @pytest.mark.parametrize('other,exdiff', [ + @pytest.mark.parametrize('other,expected_difference', [ (np.timedelta64(-123, 'ns'), -123), (np.timedelta64(1234567898, 'ns'), 1234567898), (np.timedelta64(-123, 'us'), -123000), (np.timedelta64(-123, 'ms'), -123000000) ]) - def test_timestamp_add_timedelta64_unit(self, other, exdiff): + def test_timestamp_add_timedelta64_unit(self, other, expected_difference): ts = Timestamp(datetime.utcnow()) result = ts + other valdiff = result.value - ts.value - assert valdiff == exdiff + assert valdiff == expected_difference From 534db4cd1657ad3dd0b97f73bbf34a9717458acf Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 24 Jun 2019 10:13:37 -0700 Subject: [PATCH 6/6] parametrize test --- pandas/tests/scalar/timestamp/test_arithmetic.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pandas/tests/scalar/timestamp/test_arithmetic.py b/pandas/tests/scalar/timestamp/test_arithmetic.py index 8521fddf788e5..8310b140b50e0 100644 --- a/pandas/tests/scalar/timestamp/test_arithmetic.py +++ b/pandas/tests/scalar/timestamp/test_arithmetic.py @@ -113,10 +113,12 @@ def test_addition_subtraction_preserve_frequency(self): assert (ts + td64).freq == original_freq assert (ts - td64).freq == original_freq - def test_radd_timedelta64(self): + @pytest.mark.parametrize('td', [Timedelta(hours=3), + np.timedelta64(3, 'h'), + timedelta(hours=3)]) + def test_radd_tdscalar(self, td): # GH#24775 timedelta64+Timestamp should not raise ts = Timestamp.now() - td = np.timedelta64(3, 'h') assert td + ts == ts + td @pytest.mark.parametrize('other,expected_difference', [