Skip to content

Commit c87fa18

Browse files
kawochenshoyer
authored andcommitted
ERR: GH9513 NaT methods now raise ValueError, return np.nan or return NaT
1 parent b061055 commit c87fa18

File tree

6 files changed

+88
-11
lines changed

6 files changed

+88
-11
lines changed

doc/source/whatsnew/v0.17.0.txt

+10
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,16 @@ Other API Changes
271271
- Allow passing `kwargs` to the interpolation methods (:issue:`10378`).
272272
- Serialize metadata properties of subclasses of pandas objects (:issue:`10553`).
273273

274+
- ``NaT``'s methods now either raise ``ValueError``, or return ``np.nan`` or ``NaT`` (:issue:`9513`)
275+
=========================== ==============================================================
276+
Behavior Methods
277+
=========================== ==============================================================
278+
``return np.nan`` ``weekday``, ``isoweekday``
279+
``return NaT`` ``date``, ``now``, ``replace``, ``to_datetime``, ``today``
280+
``return np.datetime64('NaT')`` ``to_datetime64`` (unchanged)
281+
``raise ValueError`` All other public methods (names not beginning with underscores)
282+
=========================== ===============================================================
283+
274284
.. _whatsnew_0170.deprecations:
275285

276286
Deprecations

pandas/tests/test_index.py

+3
Original file line numberDiff line numberDiff line change
@@ -2877,6 +2877,9 @@ def test_union(self):
28772877
result = first.union(case)
28782878
self.assertTrue(tm.equalContents(result, everything))
28792879

2880+
def test_nat(self):
2881+
self.assertIs(DatetimeIndex([np.nan])[0], pd.NaT)
2882+
28802883

28812884
class TestPeriodIndex(DatetimeLike, tm.TestCase):
28822885
_holder = PeriodIndex

pandas/tests/test_series.py

-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ def test_valid_dt_with_missing_values(self):
210210

211211
# GH 8689
212212
s = Series(date_range('20130101',periods=5,freq='D'))
213-
s_orig = s.copy()
214213
s.iloc[2] = pd.NaT
215214

216215
for attr in ['microsecond','nanosecond','second','minute','hour','day']:

pandas/tseries/index.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1392,7 +1392,8 @@ def time(self):
13921392
"""
13931393
# can't call self.map() which tries to treat func as ufunc
13941394
# and causes recursion warnings on python 2.6
1395-
return self._maybe_mask_results(_algos.arrmap_object(self.asobject.values, lambda x: x.time()))
1395+
return self._maybe_mask_results(_algos.arrmap_object(self.asobject.values,
1396+
lambda x: np.nan if x is tslib.NaT else x.time()))
13961397

13971398
@property
13981399
def date(self):

pandas/tseries/tests/test_timeseries.py

+27-2
Original file line numberDiff line numberDiff line change
@@ -941,12 +941,34 @@ def test_nat_vector_field_access(self):
941941
def test_nat_scalar_field_access(self):
942942
fields = ['year', 'quarter', 'month', 'day', 'hour',
943943
'minute', 'second', 'microsecond', 'nanosecond',
944-
'week', 'dayofyear', 'days_in_month']
944+
'week', 'dayofyear', 'days_in_month', 'daysinmonth',
945+
'dayofweek']
945946
for field in fields:
946947
result = getattr(NaT, field)
947948
self.assertTrue(np.isnan(result))
948949

949-
self.assertTrue(np.isnan(NaT.weekday()))
950+
def test_NaT_methods(self):
951+
# GH 9513
952+
raise_methods = ['astimezone', 'combine', 'ctime', 'dst', 'fromordinal',
953+
'fromtimestamp', 'isocalendar', 'isoformat',
954+
'strftime', 'strptime',
955+
'time', 'timestamp', 'timetuple', 'timetz',
956+
'toordinal', 'tzname', 'utcfromtimestamp',
957+
'utcnow', 'utcoffset', 'utctimetuple']
958+
nat_methods = ['date', 'now', 'replace', 'to_datetime', 'today']
959+
nan_methods = ['weekday', 'isoweekday']
960+
961+
for method in raise_methods:
962+
if hasattr(NaT, method):
963+
self.assertRaises(ValueError, getattr(NaT, method))
964+
965+
for method in nan_methods:
966+
if hasattr(NaT, method):
967+
self.assertTrue(np.isnan(getattr(NaT, method)()))
968+
969+
for method in nat_methods:
970+
if hasattr(NaT, method):
971+
self.assertIs(getattr(NaT, method)(), NaT)
950972

951973
def test_to_datetime_types(self):
952974

@@ -3520,6 +3542,9 @@ def check(val,unit=None,h=1,s=1,us=0):
35203542
result = Timestamp(NaT)
35213543
self.assertIs(result, NaT)
35223544

3545+
result = Timestamp('NaT')
3546+
self.assertIs(result, NaT)
3547+
35233548
def test_roundtrip(self):
35243549

35253550
# test value to string and back conversions

pandas/tslib.pyx

+46-7
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ from dateutil.relativedelta import relativedelta
5858
from dateutil.parser import DEFAULTPARSER
5959

6060
from pytz.tzinfo import BaseTzInfo as _pytz_BaseTzInfo
61-
from pandas.compat import parse_date, string_types, iteritems, StringIO
61+
from pandas.compat import parse_date, string_types, iteritems, StringIO, callable
6262

6363
import operator
6464
import collections
@@ -640,22 +640,61 @@ class NaTType(_NaT):
640640
def __long__(self):
641641
return NPY_NAT
642642

643-
def weekday(self):
644-
return np.nan
645-
646-
def toordinal(self):
647-
return -1
648-
649643
def __reduce__(self):
650644
return (__nat_unpickle, (None, ))
651645

646+
652647
fields = ['year', 'quarter', 'month', 'day', 'hour',
653648
'minute', 'second', 'millisecond', 'microsecond', 'nanosecond',
654649
'week', 'dayofyear', 'days_in_month', 'daysinmonth', 'dayofweek']
655650
for field in fields:
656651
prop = property(fget=lambda self: np.nan)
657652
setattr(NaTType, field, prop)
658653

654+
# GH9513 NaT methods (except to_datetime64) to raise, return np.nan, or return NaT
655+
# create functions that raise, for binding to NaTType
656+
def _make_error_func(func_name):
657+
def f(*args, **kwargs):
658+
raise ValueError("NaTType does not support " + func_name)
659+
f.__name__ = func_name
660+
return f
661+
662+
def _make_nat_func(func_name):
663+
def f(*args, **kwargs):
664+
return NaT
665+
f.__name__ = func_name
666+
return f
667+
668+
def _make_nan_func(func_name):
669+
def f(*args, **kwargs):
670+
return np.nan
671+
f.__name__ = func_name
672+
return f
673+
674+
_nat_methods = ['date', 'now', 'replace', 'to_datetime', 'today']
675+
676+
_nan_methods = ['weekday', 'isoweekday']
677+
678+
_implemented_methods = ['to_datetime64']
679+
_implemented_methods.extend(_nat_methods)
680+
_implemented_methods.extend(_nan_methods)
681+
682+
for _method_name in _nat_methods:
683+
# not all methods exist in all versions of Python
684+
if hasattr(NaTType, _method_name):
685+
setattr(NaTType, _method_name, _make_nat_func(_method_name))
686+
687+
for _method_name in _nan_methods:
688+
if hasattr(NaTType, _method_name):
689+
setattr(NaTType, _method_name, _make_nan_func(_method_name))
690+
691+
for _maybe_method_name in dir(NaTType):
692+
_maybe_method = getattr(NaTType, _maybe_method_name)
693+
if (callable(_maybe_method)
694+
and not _maybe_method_name.startswith("_")
695+
and _maybe_method_name not in _implemented_methods):
696+
setattr(NaTType, _maybe_method_name, _make_error_func(_maybe_method_name))
697+
659698
def __nat_unpickle(*args):
660699
# return constant defined in the module
661700
return NaT

0 commit comments

Comments
 (0)