diff --git a/doc/source/whatsnew/v0.19.0.txt b/doc/source/whatsnew/v0.19.0.txt index 9c5c36528d31b..62483816c3c8a 100644 --- a/doc/source/whatsnew/v0.19.0.txt +++ b/doc/source/whatsnew/v0.19.0.txt @@ -436,6 +436,7 @@ API changes ~~~~~~~~~~~ +- ``Timestamp.to_pydatetime`` will issue a ``UserWarning`` when ``warn=True``, and the instance has a non-zero number of nanoseconds (:issue:`14101`) - ``Panel.to_sparse`` will raise a ``NotImplementedError`` exception when called (:issue:`13778`) - ``Index.reshape`` will raise a ``NotImplementedError`` exception when called (:issue:`12882`) - Non-convertible dates in an excel date column will be returned without conversion and the column will be ``object`` dtype, rather than raising an exception (:issue:`10001`) @@ -1031,6 +1032,7 @@ Deprecations - ``Categorical.reshape`` has been deprecated and will be removed in a subsequent release (:issue:`12882`) - ``Series.reshape`` has been deprecated and will be removed in a subsequent release (:issue:`12882`) +- ``Timestamp.to_datetime`` has been deprecated in favour of ``Timestamp.to_pydatetime`` (:issue:`8254`) - ``Index.to_datetime`` and ``DatetimeIndex.to_datetime`` have been deprecated in favour of ``pd.to_datetime`` (:issue:`8254`) - ``SparseList`` has been deprecated and will be removed in a future version (:issue:`13784`) - ``DataFrame.to_html()`` and ``DataFrame.to_latex()`` have dropped the ``colSpace`` parameter in favor of ``col_space`` (:issue:`13857`) diff --git a/pandas/io/tests/json/test_pandas.py b/pandas/io/tests/json/test_pandas.py index 96756a0b2d74b..47bdd25572fc7 100644 --- a/pandas/io/tests/json/test_pandas.py +++ b/pandas/io/tests/json/test_pandas.py @@ -908,17 +908,17 @@ def test_tz_is_utc(self): ts = Timestamp('2013-01-10 05:00:00Z') self.assertEqual(exp, pd.json.dumps(ts, iso_dates=True)) - dt = ts.to_datetime() + dt = ts.to_pydatetime() self.assertEqual(exp, pd.json.dumps(dt, iso_dates=True)) ts = Timestamp('2013-01-10 00:00:00', tz='US/Eastern') self.assertEqual(exp, pd.json.dumps(ts, iso_dates=True)) - dt = ts.to_datetime() + dt = ts.to_pydatetime() self.assertEqual(exp, pd.json.dumps(dt, iso_dates=True)) ts = Timestamp('2013-01-10 00:00:00-0500') self.assertEqual(exp, pd.json.dumps(ts, iso_dates=True)) - dt = ts.to_datetime() + dt = ts.to_pydatetime() self.assertEqual(exp, pd.json.dumps(dt, iso_dates=True)) def test_tz_range_is_utc(self): diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index edf7fc444c3e1..66a5a155dd7a5 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -502,7 +502,7 @@ def test_asof(self): d = self.dateIndex[-1] self.assertEqual(self.dateIndex.asof(d + timedelta(1)), d) - d = self.dateIndex[0].to_datetime() + d = self.dateIndex[0].to_pydatetime() tm.assertIsInstance(self.dateIndex.asof(d), Timestamp) def test_asof_datetime_partial(self): diff --git a/pandas/tests/test_groupby.py b/pandas/tests/test_groupby.py index 9a82332621933..6b33fa747d8ba 100644 --- a/pandas/tests/test_groupby.py +++ b/pandas/tests/test_groupby.py @@ -779,7 +779,7 @@ def test_get_group(self): g = df.groupby('DATE') key = list(g.groups)[0] result1 = g.get_group(key) - result2 = g.get_group(Timestamp(key).to_datetime()) + result2 = g.get_group(Timestamp(key).to_pydatetime()) result3 = g.get_group(str(Timestamp(key))) assert_frame_equal(result1, result2) assert_frame_equal(result1, result3) @@ -788,7 +788,7 @@ def test_get_group(self): key = list(g.groups)[0] result1 = g.get_group(key) - result2 = g.get_group((Timestamp(key[0]).to_datetime(), key[1])) + result2 = g.get_group((Timestamp(key[0]).to_pydatetime(), key[1])) result3 = g.get_group((str(Timestamp(key[0])), key[1])) assert_frame_equal(result1, result2) assert_frame_equal(result1, result3) diff --git a/pandas/tseries/tests/test_base.py b/pandas/tseries/tests/test_base.py index 4d3c60ce39291..26e77d3ad79f3 100644 --- a/pandas/tseries/tests/test_base.py +++ b/pandas/tseries/tests/test_base.py @@ -1127,11 +1127,11 @@ def test_subtraction_ops_with_tz(self): # check that dt/dti subtraction ops with tz are validated dti = date_range('20130101', periods=3) ts = Timestamp('20130101') - dt = ts.to_datetime() + dt = ts.to_pydatetime() dti_tz = date_range('20130101', periods=3).tz_localize('US/Eastern') ts_tz = Timestamp('20130101').tz_localize('US/Eastern') ts_tz2 = Timestamp('20130101').tz_localize('CET') - dt_tz = ts_tz.to_datetime() + dt_tz = ts_tz.to_pydatetime() td = Timedelta('1 days') def _check(result, expected): diff --git a/pandas/tseries/tests/test_offsets.py b/pandas/tseries/tests/test_offsets.py index 3ec07c27ef854..6ea6382a9904a 100644 --- a/pandas/tseries/tests/test_offsets.py +++ b/pandas/tseries/tests/test_offsets.py @@ -261,8 +261,19 @@ def _check_offsetfunc_works(self, offset, funcname, dt, expected, self.assertTrue(isinstance(result, Timestamp)) self.assertEqual(result, expected) - # test nano second is preserved - result = func(Timestamp(dt) + Nano(5)) + # see gh-14101 + exp_warning = None + ts = Timestamp(dt) + Nano(5) + + if (offset_s.__class__.__name__ == 'DateOffset' and + (funcname == 'apply' or normalize) and + ts.nanosecond > 0): + exp_warning = UserWarning + + # test nanosecond is preserved + with tm.assert_produces_warning(exp_warning, + check_stacklevel=False): + result = func(ts) self.assertTrue(isinstance(result, Timestamp)) if normalize is False: self.assertEqual(result, expected + Nano(5)) @@ -289,8 +300,19 @@ def _check_offsetfunc_works(self, offset, funcname, dt, expected, self.assertTrue(isinstance(result, Timestamp)) self.assertEqual(result, expected_localize) - # test nano second is preserved - result = func(Timestamp(dt, tz=tz) + Nano(5)) + # see gh-14101 + exp_warning = None + ts = Timestamp(dt, tz=tz) + Nano(5) + + if (offset_s.__class__.__name__ == 'DateOffset' and + (funcname == 'apply' or normalize) and + ts.nanosecond > 0): + exp_warning = UserWarning + + # test nanosecond is preserved + with tm.assert_produces_warning(exp_warning, + check_stacklevel=False): + result = func(ts) self.assertTrue(isinstance(result, Timestamp)) if normalize is False: self.assertEqual(result, expected_localize + Nano(5)) diff --git a/pandas/tseries/tests/test_timeseries.py b/pandas/tseries/tests/test_timeseries.py index c19de2ff7ca35..5eb46684d1860 100644 --- a/pandas/tseries/tests/test_timeseries.py +++ b/pandas/tseries/tests/test_timeseries.py @@ -1020,7 +1020,13 @@ def test_NaT_methods(self): for method in nat_methods: if hasattr(NaT, method): - self.assertIs(getattr(NaT, method)(), NaT) + # see gh-8254 + exp_warning = None + if method == 'to_datetime': + exp_warning = FutureWarning + with tm.assert_produces_warning( + exp_warning, check_stacklevel=False): + self.assertIs(getattr(NaT, method)(), NaT) # GH 12300 self.assertEqual(NaT.isoformat(), 'NaT') diff --git a/pandas/tseries/tests/test_timezones.py b/pandas/tseries/tests/test_timezones.py index 470aafafec547..7ec0d09c20841 100644 --- a/pandas/tseries/tests/test_timezones.py +++ b/pandas/tseries/tests/test_timezones.py @@ -169,7 +169,7 @@ def test_timestamp_to_datetime_tzoffset(self): from dateutil.tz import tzoffset tzinfo = tzoffset(None, 7200) expected = Timestamp('3/11/2012 04:00', tz=tzinfo) - result = Timestamp(expected.to_datetime()) + result = Timestamp(expected.to_pydatetime()) self.assertEqual(expected, result) def test_timedelta_push_over_dst_boundary(self): diff --git a/pandas/tseries/tests/test_tslib.py b/pandas/tseries/tests/test_tslib.py index 22bb3bddbc742..6cee45df2a63c 100644 --- a/pandas/tseries/tests/test_tslib.py +++ b/pandas/tseries/tests/test_tslib.py @@ -47,12 +47,17 @@ def test_max_valid(self): def test_to_datetime_bijective(self): # Ensure that converting to datetime and back only loses precision # by going from nanoseconds to microseconds. - self.assertEqual( - Timestamp(Timestamp.max.to_pydatetime()).value / 1000, - Timestamp.max.value / 1000) - self.assertEqual( - Timestamp(Timestamp.min.to_pydatetime()).value / 1000, - Timestamp.min.value / 1000) + exp_warning = None if Timestamp.max.nanosecond == 0 else UserWarning + with tm.assert_produces_warning(exp_warning, check_stacklevel=False): + self.assertEqual( + Timestamp(Timestamp.max.to_pydatetime()).value / 1000, + Timestamp.max.value / 1000) + + exp_warning = None if Timestamp.min.nanosecond == 0 else UserWarning + with tm.assert_produces_warning(exp_warning, check_stacklevel=False): + self.assertEqual( + Timestamp(Timestamp.min.to_pydatetime()).value / 1000, + Timestamp.min.value / 1000) class TestTimestamp(tm.TestCase): @@ -616,6 +621,26 @@ def test_pprint(self): 'foo': 1}""" self.assertEqual(result, expected) + def to_datetime_depr(self): + # see gh-8254 + ts = Timestamp('2011-01-01') + + with tm.assert_produces_warning(FutureWarning, + check_stacklevel=False): + expected = datetime.datetime(2011, 1, 1) + result = ts.to_datetime() + self.assertEqual(result, expected) + + def to_pydatetime_nonzero_nano(self): + ts = Timestamp('2011-01-01 9:00:00.123456789') + + # Warn the user of data loss (nanoseconds). + with tm.assert_produces_warning(UserWarning, + check_stacklevel=False): + expected = datetime.datetime(2011, 1, 1, 9, 0, 0, 123456) + result = ts.to_pydatetime() + self.assertEqual(result, expected) + class TestDatetimeParsingWrappers(tm.TestCase): def test_does_not_convert_mixed_integer(self): diff --git a/pandas/tslib.pyx b/pandas/tslib.pyx index c1b990c417553..c9e85c5741410 100644 --- a/pandas/tslib.pyx +++ b/pandas/tslib.pyx @@ -1,5 +1,7 @@ # cython: profile=False +import warnings + cimport numpy as np from numpy cimport (int8_t, int32_t, int64_t, import_array, ndarray, NPY_INT64, NPY_DATETIME, NPY_TIMEDELTA) @@ -637,22 +639,6 @@ class Timestamp(_Timestamp): return Timestamp(datetime.replace(self, **kwds), freq=self.freq) - def to_pydatetime(self, warn=True): - """ - If warn=True, issue warning if nanoseconds is nonzero - """ - cdef: - pandas_datetimestruct dts - _TSObject ts - - if self.nanosecond != 0 and warn: - print 'Warning: discarding nonzero nanoseconds' - ts = convert_to_tsobject(self, self.tzinfo, None, 0, 0) - - return datetime(ts.dts.year, ts.dts.month, ts.dts.day, - ts.dts.hour, ts.dts.min, ts.dts.sec, - ts.dts.us, ts.tzinfo) - def isoformat(self, sep='T'): base = super(_Timestamp, self).isoformat(sep=sep) if self.nanosecond == 0: @@ -805,11 +791,11 @@ def _make_nan_func(func_name): f.__name__ = func_name return f -_nat_methods = ['date', 'now', 'replace', 'to_datetime', 'today'] +_nat_methods = ['date', 'now', 'replace', 'to_pydatetime', 'today'] _nan_methods = ['weekday', 'isoweekday', 'total_seconds'] -_implemented_methods = ['to_datetime64', 'isoformat'] +_implemented_methods = ['to_datetime', 'to_datetime64', 'isoformat'] _implemented_methods.extend(_nat_methods) _implemented_methods.extend(_nan_methods) @@ -986,7 +972,7 @@ cdef class _Timestamp(datetime): ots = other elif isinstance(other, datetime): if self.nanosecond == 0: - val = self.to_datetime() + val = self.to_pydatetime() return PyObject_RichCompareBool(val, other, op) try: @@ -1048,7 +1034,7 @@ cdef class _Timestamp(datetime): cdef bint _compare_outside_nanorange(_Timestamp self, datetime other, int op) except -1: - cdef datetime dtval = self.to_datetime() + cdef datetime dtval = self.to_pydatetime() self._assert_tzawareness_compat(other) @@ -1078,9 +1064,28 @@ cdef class _Timestamp(datetime): raise TypeError('Cannot compare tz-naive and tz-aware timestamps') cpdef datetime to_datetime(_Timestamp self): + """ + DEPRECATED: use :meth:`to_pydatetime` instead. + + Convert a Timestamp object to a native Python datetime object. + """ + warnings.warn("to_datetime is deprecated. Use self.to_pydatetime()", + FutureWarning, stacklevel=2) + return self.to_pydatetime(warn=False) + + cpdef datetime to_pydatetime(_Timestamp self, warn=True): + """ + Convert a Timestamp object to a native Python datetime object. + + If warn=True, issue a warning if nanoseconds is nonzero. + """ cdef: pandas_datetimestruct dts _TSObject ts + + if self.nanosecond != 0 and warn: + warnings.warn("Discarding nonzero nanoseconds in conversion", + UserWarning, stacklevel=2) ts = convert_to_tsobject(self, self.tzinfo, None, 0, 0) dts = ts.dts return datetime(dts.year, dts.month, dts.day,