diff --git a/doc/source/whatsnew/v0.17.0.txt b/doc/source/whatsnew/v0.17.0.txt index e77532b2fe432..069425984b1b2 100644 --- a/doc/source/whatsnew/v0.17.0.txt +++ b/doc/source/whatsnew/v0.17.0.txt @@ -232,6 +232,16 @@ Other API Changes - Serialize metadata properties of subclasses of pandas objects (:issue:`10553`). +- ``NaT``'s methods now either raise ``ValueError``, or return ``np.nan`` or ``NaT`` (:issue:`9513`) +=========================== ============================================================== +Behavior Methods +=========================== ============================================================== +``return np.nan`` ``weekday``, ``isoweekday`` +``return NaT`` ``date``, ``now``, ``replace``, ``to_datetime``, ``today`` +``return np.datetime64('NaT')`` ``to_datetime64`` (unchanged) +``raise ValueError`` All other public methods (names not beginning with underscores) +=========================== =============================================================== + .. _whatsnew_0170.deprecations: Deprecations diff --git a/pandas/tests/test_index.py b/pandas/tests/test_index.py index 3d901837f5123..41c814f8943b2 100644 --- a/pandas/tests/test_index.py +++ b/pandas/tests/test_index.py @@ -2879,6 +2879,9 @@ def test_union(self): result = first.union(case) self.assertTrue(tm.equalContents(result, everything)) + def test_nat(self): + self.assertIs(DatetimeIndex([np.nan])[0], pd.NaT) + class TestPeriodIndex(DatetimeLike, tm.TestCase): _holder = PeriodIndex diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py index 7326d7a9d811d..826eddb63a5d5 100644 --- a/pandas/tests/test_series.py +++ b/pandas/tests/test_series.py @@ -210,7 +210,6 @@ def test_valid_dt_with_missing_values(self): # GH 8689 s = Series(date_range('20130101',periods=5,freq='D')) - s_orig = s.copy() s.iloc[2] = pd.NaT for attr in ['microsecond','nanosecond','second','minute','hour','day']: diff --git a/pandas/tseries/index.py b/pandas/tseries/index.py index a8b6fb4389459..6cf972d4d7a8a 100644 --- a/pandas/tseries/index.py +++ b/pandas/tseries/index.py @@ -1392,7 +1392,8 @@ def time(self): """ # can't call self.map() which tries to treat func as ufunc # and causes recursion warnings on python 2.6 - return self._maybe_mask_results(_algos.arrmap_object(self.asobject.values, lambda x: x.time())) + return self._maybe_mask_results(_algos.arrmap_object(self.asobject.values, + lambda x: np.nan if x is tslib.NaT else x.time())) @property def date(self): diff --git a/pandas/tseries/tests/test_timeseries.py b/pandas/tseries/tests/test_timeseries.py index c8b96076b26bd..a6c5dc23250b8 100644 --- a/pandas/tseries/tests/test_timeseries.py +++ b/pandas/tseries/tests/test_timeseries.py @@ -941,12 +941,34 @@ def test_nat_vector_field_access(self): def test_nat_scalar_field_access(self): fields = ['year', 'quarter', 'month', 'day', 'hour', 'minute', 'second', 'microsecond', 'nanosecond', - 'week', 'dayofyear', 'days_in_month'] + 'week', 'dayofyear', 'days_in_month', 'daysinmonth', + 'dayofweek'] for field in fields: result = getattr(NaT, field) self.assertTrue(np.isnan(result)) - self.assertTrue(np.isnan(NaT.weekday())) + def test_NaT_methods(self): + # GH 9513 + raise_methods = ['astimezone', 'combine', 'ctime', 'dst', 'fromordinal', + 'fromtimestamp', 'isocalendar', 'isoformat', + 'strftime', 'strptime', + 'time', 'timestamp', 'timetuple', 'timetz', + 'toordinal', 'tzname', 'utcfromtimestamp', + 'utcnow', 'utcoffset', 'utctimetuple'] + nat_methods = ['date', 'now', 'replace', 'to_datetime', 'today'] + nan_methods = ['weekday', 'isoweekday'] + + for method in raise_methods: + if hasattr(NaT, method): + self.assertRaises(ValueError, getattr(NaT, method)) + + for method in nan_methods: + if hasattr(NaT, method): + self.assertTrue(np.isnan(getattr(NaT, method)())) + + for method in nat_methods: + if hasattr(NaT, method): + self.assertIs(getattr(NaT, method)(), NaT) def test_to_datetime_types(self): @@ -3520,6 +3542,9 @@ def check(val,unit=None,h=1,s=1,us=0): result = Timestamp(NaT) self.assertIs(result, NaT) + result = Timestamp('NaT') + self.assertIs(result, NaT) + def test_roundtrip(self): # test value to string and back conversions diff --git a/pandas/tslib.pyx b/pandas/tslib.pyx index 168bb754250e3..a2fc9b07b16a1 100644 --- a/pandas/tslib.pyx +++ b/pandas/tslib.pyx @@ -58,7 +58,7 @@ from dateutil.relativedelta import relativedelta from dateutil.parser import DEFAULTPARSER from pytz.tzinfo import BaseTzInfo as _pytz_BaseTzInfo -from pandas.compat import parse_date, string_types, iteritems, StringIO +from pandas.compat import parse_date, string_types, iteritems, StringIO, callable import operator import collections @@ -640,15 +640,10 @@ class NaTType(_NaT): def __long__(self): return NPY_NAT - def weekday(self): - return np.nan - - def toordinal(self): - return -1 - def __reduce__(self): return (__nat_unpickle, (None, )) + fields = ['year', 'quarter', 'month', 'day', 'hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond', 'week', 'dayofyear', 'days_in_month', 'daysinmonth', 'dayofweek'] @@ -656,6 +651,50 @@ for field in fields: prop = property(fget=lambda self: np.nan) setattr(NaTType, field, prop) +# GH9513 NaT methods (except to_datetime64) to raise, return np.nan, or return NaT +# create functions that raise, for binding to NaTType +def _make_error_func(func_name): + def f(*args, **kwargs): + raise ValueError("NaTType does not support " + func_name) + f.__name__ = func_name + return f + +def _make_nat_func(func_name): + def f(*args, **kwargs): + return NaT + f.__name__ = func_name + return f + +def _make_nan_func(func_name): + def f(*args, **kwargs): + return np.nan + f.__name__ = func_name + return f + +_nat_methods = ['date', 'now', 'replace', 'to_datetime', 'today'] + +_nan_methods = ['weekday', 'isoweekday'] + +_implemented_methods = ['to_datetime64'] +_implemented_methods.extend(_nat_methods) +_implemented_methods.extend(_nan_methods) + +for _method_name in _nat_methods: + # not all methods exist in all versions of Python + if hasattr(NaTType, _method_name): + setattr(NaTType, _method_name, _make_nat_func(_method_name)) + +for _method_name in _nan_methods: + if hasattr(NaTType, _method_name): + setattr(NaTType, _method_name, _make_nan_func(_method_name)) + +for _maybe_method_name in dir(NaTType): + _maybe_method = getattr(NaTType, _maybe_method_name) + if (callable(_maybe_method) + and not _maybe_method_name.startswith("_") + and _maybe_method_name not in _implemented_methods): + setattr(NaTType, _maybe_method_name, _make_error_func(_maybe_method_name)) + def __nat_unpickle(*args): # return constant defined in the module return NaT