From 155a5491b8e0f45749eaa5e91ecde60fe6d51f25 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Wed, 10 Apr 2019 20:30:23 -0400 Subject: [PATCH 01/11] BUG: Fix NaT comparisons with Timedelta (#26039) --- doc/source/whatsnew/v0.25.0.rst | 2 +- pandas/_libs/tslibs/timedeltas.pyx | 17 +++++++------ pandas/tests/arithmetic/test_timedelta64.py | 28 +++++++++++++++++++++ 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index c441244b4415d..b38bc92c6bd86 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -273,7 +273,7 @@ Datetimelike Timedelta ^^^^^^^^^ -- +- Bug with comparisons between :class:`Timedelta` and ``NaT`` raising ``TypeError`` (:issue:`26039`) - - diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 8b71d64db26c6..71969586df905 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -779,13 +779,16 @@ cdef class _Timedelta(timedelta): return PyObject_RichCompare(np.array([self]), other, op) return PyObject_RichCompare(other, self, reverse_ops[op]) else: - if op == Py_EQ: - return False - elif op == Py_NE: - return True - raise TypeError('Cannot compare type {cls} with type {other}' - .format(cls=type(self).__name__, - other=type(other).__name__)) + if other is NaT: + return PyObject_RichCompare(other, self, reverse_ops[op]) + else: + if op == Py_EQ: + return False + elif op == Py_NE: + return True + raise TypeError('Cannot compare type {cls} with type {other}' + .format(cls=type(self).__name__, + other=type(other).__name__)) return cmp_scalar(self.value, ots.value, op) diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index 0faed74d4a021..a3495ecff449e 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -441,6 +441,34 @@ def test_timedelta(self, freq): tm.assert_index_equal(result1, result4) tm.assert_index_equal(result2, result3) + def test_timedelta_nat_comparisons(self): + # GH 26039 + td = pd.Timedelta(0) + + result = td > NaT + assert result == False + + result = td >= NaT + assert result == False + + result = td < NaT + assert result == False + + result = td <= NaT + assert result == False + + result = NaT > td + assert result == False + + result = NaT >= td + assert result == False + + result = NaT < td + assert result == False + + result = NaT <= td + assert result == False + class TestAddSubNaTMasking(object): # TODO: parametrize over boxes From 7ff5f9895b9c1ccbad9b7030c72ab7838411b875 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Wed, 10 Apr 2019 20:35:59 -0400 Subject: [PATCH 02/11] formatting, fix flake8 errors --- pandas/tests/arithmetic/test_timedelta64.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index a3495ecff449e..4973957d41a7b 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -446,28 +446,28 @@ def test_timedelta_nat_comparisons(self): td = pd.Timedelta(0) result = td > NaT - assert result == False + assert result is False result = td >= NaT - assert result == False + assert result is False result = td < NaT - assert result == False + assert result is False result = td <= NaT - assert result == False + assert result is False result = NaT > td - assert result == False + assert result is False result = NaT >= td - assert result == False + assert result is False result = NaT < td - assert result == False + assert result is False result = NaT <= td - assert result == False + assert result is False class TestAddSubNaTMasking(object): From b3ca30caecf627f1d09f65d6de2cd17a19e80b93 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Wed, 10 Apr 2019 20:37:36 -0400 Subject: [PATCH 03/11] missed formatting fix --- pandas/_libs/tslibs/timedeltas.pyx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 71969586df905..c54d05f1beffd 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -786,7 +786,8 @@ cdef class _Timedelta(timedelta): return False elif op == Py_NE: return True - raise TypeError('Cannot compare type {cls} with type {other}' + raise TypeError('Cannot compare type {cls} with ' + 'type {other}' .format(cls=type(self).__name__, other=type(other).__name__)) From f97a8400dcf03c7edc85edabd7491015b3e0c4fc Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Thu, 11 Apr 2019 17:45:33 -0400 Subject: [PATCH 04/11] move test --- pandas/tests/arithmetic/test_timedelta64.py | 28 --------------- .../tests/scalar/timedelta/test_arithmetic.py | 34 +++++++++++++++++++ 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index 4973957d41a7b..0faed74d4a021 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -441,34 +441,6 @@ def test_timedelta(self, freq): tm.assert_index_equal(result1, result4) tm.assert_index_equal(result2, result3) - def test_timedelta_nat_comparisons(self): - # GH 26039 - td = pd.Timedelta(0) - - result = td > NaT - assert result is False - - result = td >= NaT - assert result is False - - result = td < NaT - assert result is False - - result = td <= NaT - assert result is False - - result = NaT > td - assert result is False - - result = NaT >= td - assert result is False - - result = NaT < td - assert result is False - - result = NaT <= td - assert result is False - class TestAddSubNaTMasking(object): # TODO: parametrize over boxes diff --git a/pandas/tests/scalar/timedelta/test_arithmetic.py b/pandas/tests/scalar/timedelta/test_arithmetic.py index b6ad251d598ab..c0c705be5cace 100644 --- a/pandas/tests/scalar/timedelta/test_arithmetic.py +++ b/pandas/tests/scalar/timedelta/test_arithmetic.py @@ -689,3 +689,37 @@ def test_rdivmod_invalid(self): def test_td_op_timedelta_timedeltalike_array(self, op, arr): with pytest.raises(TypeError): op(arr, Timedelta('1D')) + + +class TestTimedeltaCompare(): + """ + Tests for Timedelta comparisons. + """ + + def test_timedelta_nat_comparisons(self): + # GH 26039 + td = pd.Timedelta(0) + + result = td > NaT + assert result is False + + result = td >= NaT + assert result is False + + result = td < NaT + assert result is False + + result = td <= NaT + assert result is False + + result = NaT > td + assert result is False + + result = NaT >= td + assert result is False + + result = NaT < td + assert result is False + + result = NaT <= td + assert result is False \ No newline at end of file From b85fae463e6b3dc266d210871f4283d4b1fccd86 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Thu, 11 Apr 2019 17:47:56 -0400 Subject: [PATCH 05/11] add newline --- pandas/tests/scalar/timedelta/test_arithmetic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/scalar/timedelta/test_arithmetic.py b/pandas/tests/scalar/timedelta/test_arithmetic.py index c0c705be5cace..262d184480a61 100644 --- a/pandas/tests/scalar/timedelta/test_arithmetic.py +++ b/pandas/tests/scalar/timedelta/test_arithmetic.py @@ -722,4 +722,4 @@ def test_timedelta_nat_comparisons(self): assert result is False result = NaT <= td - assert result is False \ No newline at end of file + assert result is False From 8884962edb33fea82fab51c06594621322d44570 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Fri, 12 Apr 2019 08:37:28 -0400 Subject: [PATCH 06/11] moved and updated test --- pandas/tests/scalar/test_nat.py | 13 +++++++ .../tests/scalar/timedelta/test_arithmetic.py | 34 ------------------- 2 files changed, 13 insertions(+), 34 deletions(-) diff --git a/pandas/tests/scalar/test_nat.py b/pandas/tests/scalar/test_nat.py index 43747ea8621d9..ab4f345ca3474 100644 --- a/pandas/tests/scalar/test_nat.py +++ b/pandas/tests/scalar/test_nat.py @@ -348,3 +348,16 @@ def test_to_numpy_alias(): result = NaT.to_numpy() assert isna(expected) and isna(result) + + +@pytest.mark.parametrize("op", [ + lambda a, b: a > b, lambda a, b: a >= b, + lambda a, b: a < b, lambda a, b: a <= b +]) +@pytest.mark.parametrize("other", [ + Timedelta(0), Timestamp(0) +]) +def test_nat_comparisons(op, other): + # GH 26039 + assert op(NaT, other) is False + assert op(other, NaT) is False diff --git a/pandas/tests/scalar/timedelta/test_arithmetic.py b/pandas/tests/scalar/timedelta/test_arithmetic.py index 262d184480a61..b6ad251d598ab 100644 --- a/pandas/tests/scalar/timedelta/test_arithmetic.py +++ b/pandas/tests/scalar/timedelta/test_arithmetic.py @@ -689,37 +689,3 @@ def test_rdivmod_invalid(self): def test_td_op_timedelta_timedeltalike_array(self, op, arr): with pytest.raises(TypeError): op(arr, Timedelta('1D')) - - -class TestTimedeltaCompare(): - """ - Tests for Timedelta comparisons. - """ - - def test_timedelta_nat_comparisons(self): - # GH 26039 - td = pd.Timedelta(0) - - result = td > NaT - assert result is False - - result = td >= NaT - assert result is False - - result = td < NaT - assert result is False - - result = td <= NaT - assert result is False - - result = NaT > td - assert result is False - - result = NaT >= td - assert result is False - - result = NaT < td - assert result is False - - result = NaT <= td - assert result is False From 1ec585992923d8e2681038052e489bad7a83c8b4 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Sun, 21 Apr 2019 13:53:41 -0400 Subject: [PATCH 07/11] added fixture, updated test --- pandas/conftest.py | 13 +++++++++++++ pandas/tests/scalar/test_nat.py | 9 ++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/pandas/conftest.py b/pandas/conftest.py index 1cb518f426299..8f885c3aae928 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -218,6 +218,19 @@ def all_compare_operators(request): return request.param +@pytest.fixture(params=['__le__', '__lt__', '__ge__', '__gt__']) +def compare_operators_no_eq_ne(request): + """ + Fixture for dunder names for compare operations except == and != + + * >= + * > + * < + * <= + """ + return request.param + + @pytest.fixture(params=[None, 'gzip', 'bz2', 'zip', 'xz']) def compression(request): """ diff --git a/pandas/tests/scalar/test_nat.py b/pandas/tests/scalar/test_nat.py index ab4f345ca3474..9575cc627b252 100644 --- a/pandas/tests/scalar/test_nat.py +++ b/pandas/tests/scalar/test_nat.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +import operator import numpy as np import pytest @@ -350,14 +351,12 @@ def test_to_numpy_alias(): assert isna(expected) and isna(result) -@pytest.mark.parametrize("op", [ - lambda a, b: a > b, lambda a, b: a >= b, - lambda a, b: a < b, lambda a, b: a <= b -]) @pytest.mark.parametrize("other", [ Timedelta(0), Timestamp(0) ]) -def test_nat_comparisons(op, other): +def test_nat_comparisons(compare_operators_no_eq_ne, other): # GH 26039 + short_opname = compare_operators_no_eq_ne.strip('_') + op = getattr(operator, short_opname) assert op(NaT, other) is False assert op(other, NaT) is False From 50aedec36de5865e7e51e17b12b528a8f0d38cef Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Sun, 21 Apr 2019 14:30:18 -0400 Subject: [PATCH 08/11] improved test --- pandas/tests/scalar/test_nat.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/tests/scalar/test_nat.py b/pandas/tests/scalar/test_nat.py index 9575cc627b252..fa0aa1996e307 100644 --- a/pandas/tests/scalar/test_nat.py +++ b/pandas/tests/scalar/test_nat.py @@ -356,7 +356,6 @@ def test_to_numpy_alias(): ]) def test_nat_comparisons(compare_operators_no_eq_ne, other): # GH 26039 - short_opname = compare_operators_no_eq_ne.strip('_') - op = getattr(operator, short_opname) + op = getattr(operator, compare_operators_no_eq_ne) assert op(NaT, other) is False assert op(other, NaT) is False From 2e87faece2aa239986901093a00dbaf7b0c09fe7 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Sun, 21 Apr 2019 17:51:39 -0400 Subject: [PATCH 09/11] resort ifs, update test --- pandas/tests/scalar/test_nat.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pandas/tests/scalar/test_nat.py b/pandas/tests/scalar/test_nat.py index fa0aa1996e307..0ae4d107d85bd 100644 --- a/pandas/tests/scalar/test_nat.py +++ b/pandas/tests/scalar/test_nat.py @@ -1,5 +1,4 @@ from datetime import datetime, timedelta -import operator import numpy as np import pytest @@ -356,6 +355,5 @@ def test_to_numpy_alias(): ]) def test_nat_comparisons(compare_operators_no_eq_ne, other): # GH 26039 - op = getattr(operator, compare_operators_no_eq_ne) - assert op(NaT, other) is False - assert op(other, NaT) is False + assert getattr(NaT, compare_operators_no_eq_ne)(other) is False + assert getattr(other, compare_operators_no_eq_ne)(NaT) is False From 122ce89ff9f003c1c41bd4b41264abdd6c27a157 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Sun, 21 Apr 2019 17:51:48 -0400 Subject: [PATCH 10/11] missed file commit --- pandas/_libs/tslibs/timedeltas.pyx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index c54d05f1beffd..c1edb664a79a7 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -781,15 +781,14 @@ cdef class _Timedelta(timedelta): else: if other is NaT: return PyObject_RichCompare(other, self, reverse_ops[op]) - else: - if op == Py_EQ: - return False - elif op == Py_NE: - return True - raise TypeError('Cannot compare type {cls} with ' - 'type {other}' - .format(cls=type(self).__name__, - other=type(other).__name__)) + elif op == Py_EQ: + return False + elif op == Py_NE: + return True + raise TypeError('Cannot compare type {cls} with ' + 'type {other}' + .format(cls=type(self).__name__, + other=type(other).__name__)) return cmp_scalar(self.value, ots.value, op) From cef27946d25dc8877bb5068394c38a8ffd0331e8 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Sat, 27 Apr 2019 10:29:28 -0400 Subject: [PATCH 11/11] empty commit