Skip to content

DEPR: Deprecate Timestamp.to_datetime #14101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/source/whatsnew/v0.19.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down Expand Up @@ -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`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add entry about the new warning

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

- ``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`)
Expand Down
6 changes: 3 additions & 3 deletions pandas/io/tests/json/test_pandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/indexes/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/test_groupby.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions pandas/tseries/tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
30 changes: 26 additions & 4 deletions pandas/tseries/tests/test_offsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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))
Expand Down
8 changes: 7 additions & 1 deletion pandas/tseries/tests/test_timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
2 changes: 1 addition & 1 deletion pandas/tseries/tests/test_timezones.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
37 changes: 31 additions & 6 deletions pandas/tseries/tests/test_tslib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
45 changes: 25 additions & 20 deletions pandas/tslib.pyx
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm maybe RuntimeWarning is better? not sure when that is typically used

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think user warning makes more sense here because this is converting user-provided data.

ts = convert_to_tsobject(self, self.tzinfo, None, 0, 0)
dts = ts.dts
return datetime(dts.year, dts.month, dts.day,
Expand Down