Skip to content

Commit a8f68f9

Browse files
committed
BUG: Fix Timestamp compares to NaT
For example Timestamp('now') > pd.NaT should return the same result as pd.NaT < Timestamp('now') but before this commit it didn't because when Timestamp was on the left, the values were compared whereas if NaT is on the left then the truth values are hard-coded based on rules like NaN.
1 parent 3f509ac commit a8f68f9

File tree

1 file changed

+73
-93
lines changed

1 file changed

+73
-93
lines changed

pandas/tslib.pyx

+73-93
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ from cpython cimport (
99
PyTypeObject,
1010
PyFloat_Check,
1111
PyObject_RichCompareBool,
12-
PyString_Check
12+
PyObject_RichCompare,
13+
PyString_Check,
14+
Py_GT, Py_GE, Py_EQ, Py_NE, Py_LT, Py_LE
1315
)
1416

1517
# Cython < 0.17 doesn't have this in cpython
1618
cdef extern from "Python.h":
1719
cdef PyTypeObject *Py_TYPE(object)
20+
int PySlice_Check(object)
1821

1922

2023
from libc.stdlib cimport free
@@ -30,9 +33,6 @@ from datetime import timedelta, datetime
3033
from datetime import time as datetime_time
3134
from pandas.compat import parse_date
3235

33-
cdef extern from "Python.h":
34-
int PySlice_Check(object)
35-
3636
# initialize numpy
3737
import_array()
3838
#import_ufunc()
@@ -437,9 +437,35 @@ def apply_offset(ndarray[object] values, object offset):
437437

438438
result = np.empty(n, dtype='M8[ns]')
439439
new_values = result.view('i8')
440-
pass
441440

442441

442+
cdef inline bint _cmp_scalar(int64_t lhs, int64_t rhs, int op) except -1:
443+
if op == Py_EQ:
444+
return lhs == rhs
445+
elif op == Py_NE:
446+
return lhs != rhs
447+
elif op == Py_LT:
448+
return lhs < rhs
449+
elif op == Py_LE:
450+
return lhs <= rhs
451+
elif op == Py_GT:
452+
return lhs > rhs
453+
elif op == Py_GE:
454+
return lhs >= rhs
455+
456+
457+
cdef int _reverse_ops[6]
458+
459+
_reverse_ops[Py_LT] = Py_GT
460+
_reverse_ops[Py_LE] = Py_GE
461+
_reverse_ops[Py_EQ] = Py_EQ
462+
_reverse_ops[Py_NE] = Py_NE
463+
_reverse_ops[Py_GT] = Py_LT
464+
_reverse_ops[Py_GE] = Py_LE
465+
466+
467+
cdef char* _NDIM_STRING = "ndim"
468+
443469
# This is PITA. Because we inherit from datetime, which has very specific
444470
# construction requirements, we need to do object instantiation in python
445471
# (see Timestamp class above). This will serve as a C extension type that
@@ -456,12 +482,15 @@ cdef class _Timestamp(datetime):
456482
return datetime.__hash__(self)
457483

458484
def __richcmp__(_Timestamp self, object other, int op):
459-
cdef _Timestamp ots
460-
cdef int ndim = getattr(other, 'ndim', -1)
485+
cdef:
486+
_Timestamp ots
487+
int ndim = getattr(other, _NDIM_STRING, -1)
461488

462489
if isinstance(other, _Timestamp):
490+
if isinstance(other, NaTType):
491+
return PyObject_RichCompare(other, self, _reverse_ops[op])
463492
ots = other
464-
elif type(other) is datetime:
493+
elif isinstance(other, datetime):
465494
if self.nanosecond == 0:
466495
val = self.to_datetime()
467496
return PyObject_RichCompareBool(val, other, op)
@@ -476,74 +505,40 @@ cdef class _Timestamp(datetime):
476505
if isinstance(other, np.datetime64):
477506
other = Timestamp(other)
478507
else:
479-
raise TypeError("Cannot compare Timestamp with type"
480-
" %r" % type(other).__name__)
481-
if op == 2: # ==
482-
return other == self
483-
elif op == 3: # !=
484-
return other != self
485-
elif op == 0: # <
486-
return other > self
487-
elif op == 1: # <=
488-
return other >= self
489-
elif op == 4: # >
490-
return other < self
491-
elif op == 5: # >=
492-
return other <= self
508+
raise TypeError('Cannot compare type %r with type %r' %
509+
(type(self).__name__,
510+
type(other).__name__))
511+
return PyObject_RichCompare(other, self, _reverse_ops[op])
493512
else:
494-
if op == 2:
513+
if op == Py_EQ:
495514
return False
496-
elif op == 3:
515+
elif op == Py_NE:
497516
return True
498-
else:
499-
raise TypeError('Cannot compare Timestamp with '
500-
'{0!r}'.format(other.__class__.__name__))
517+
raise TypeError('Cannot compare type %r with type %r' %
518+
(type(self).__name__, type(other).__name__))
501519

502520
self._assert_tzawareness_compat(other)
503-
504-
if op == 2: # ==
505-
return self.value == ots.value
506-
elif op == 3: # !=
507-
return self.value != ots.value
508-
elif op == 0: # <
509-
return self.value < ots.value
510-
elif op == 1: # <=
511-
return self.value <= ots.value
512-
elif op == 4: # >
513-
return self.value > ots.value
514-
elif op == 5: # >=
515-
return self.value >= ots.value
521+
return _cmp_scalar(self.value, ots.value, op)
516522

517523
cdef _compare_outside_nanorange(self, object other, int op):
518-
dtval = self.to_datetime()
524+
cdef datetime dtval = self.to_datetime()
519525

520526
self._assert_tzawareness_compat(other)
521527

522528
if self.nanosecond == 0:
523-
if op == 2: # ==
524-
return dtval == other
525-
elif op == 3: # !=
526-
return dtval != other
527-
elif op == 0: # <
528-
return dtval < other
529-
elif op == 1: # <=
530-
return dtval <= other
531-
elif op == 4: # >
532-
return dtval > other
533-
elif op == 5: # >=
534-
return dtval >= other
529+
return PyObject_RichCompare(dtval, other, op)
535530
else:
536-
if op == 2: # ==
531+
if op == Py_EQ:
537532
return False
538-
elif op == 3: # !=
533+
elif op == Py_NE:
539534
return True
540-
elif op == 0: # <
535+
elif op == Py_LT:
541536
return dtval < other
542-
elif op == 1: # <=
537+
elif op == Py_LE:
543538
return dtval < other
544-
elif op == 4: # >
539+
elif op == Py_GT:
545540
return dtval >= other
546-
elif op == 5: # >=
541+
elif op == Py_GE:
547542
return dtval >= other
548543

549544
cdef _assert_tzawareness_compat(self, object other):
@@ -601,49 +596,34 @@ cdef inline bint is_timestamp(object o):
601596
return Py_TYPE(o) == ts_type # isinstance(o, Timestamp)
602597

603598

599+
cdef bint _nat_scalar_rules[6]
600+
601+
_nat_scalar_rules[Py_EQ] = False
602+
_nat_scalar_rules[Py_NE] = True
603+
_nat_scalar_rules[Py_LT] = False
604+
_nat_scalar_rules[Py_LE] = False
605+
_nat_scalar_rules[Py_GT] = False
606+
_nat_scalar_rules[Py_GE] = False
607+
608+
604609
cdef class _NaT(_Timestamp):
605610

606611
def __hash__(_NaT self):
607612
# py3k needs this defined here
608613
return hash(self.value)
609614

610615
def __richcmp__(_NaT self, object other, int op):
611-
# if not isinstance(other, (_NaT, _Timestamp)):
612-
# raise TypeError('Cannot compare %s with NaT' % type(other))
613616
cdef int ndim = getattr(other, 'ndim', -1)
614617

615-
if ndim != -1:
616-
if ndim == 0:
617-
if isinstance(other, np.datetime64):
618-
other = Timestamp(other)
619-
else:
620-
raise TypeError("Cannot compare NaT with type "
621-
"%r" % type(other).__name__)
622-
if op == 2: # ==
623-
return other == self
624-
elif op == 3: # !=
625-
return other != self
626-
elif op == 0: # <
627-
return other > self
628-
elif op == 1: # <=
629-
return other >= self
630-
elif op == 4: # >
631-
return other < self
632-
elif op == 5: # >=
633-
return other <= self
634-
else:
635-
if op == 2: # ==
636-
return False
637-
elif op == 3: # !=
638-
return True
639-
elif op == 0: # <
640-
return False
641-
elif op == 1: # <=
642-
return False
643-
elif op == 4: # >
644-
return False
645-
elif op == 5: # >=
646-
return False
618+
if ndim == -1:
619+
return _nat_scalar_rules[op]
620+
621+
if ndim == 0:
622+
if isinstance(other, np.datetime64):
623+
other = Timestamp(other)
624+
else:
625+
raise TypeError("asdf")
626+
return PyObject_RichCompare(other, self, _reverse_ops[op])
647627

648628

649629
def _delta_to_nanoseconds(delta):

0 commit comments

Comments
 (0)