Skip to content

Commit 24501d9

Browse files
jbrockmendelaeltanawy
authored andcommitted
Fix incorrect DTI/TDI indexing; warn before dropping tzinfo (pandas-dev#22549)
1 parent 1faac78 commit 24501d9

File tree

8 files changed

+77
-10
lines changed

8 files changed

+77
-10
lines changed

doc/source/whatsnew/v0.24.0.txt

+4-1
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ Datetimelike API Changes
525525
- :class:`DateOffset` objects are now immutable. Attempting to alter one of these will now raise ``AttributeError`` (:issue:`21341`)
526526
- :class:`PeriodIndex` subtraction of another ``PeriodIndex`` will now return an object-dtype :class:`Index` of :class:`DateOffset` objects instead of raising a ``TypeError`` (:issue:`20049`)
527527
- :func:`cut` and :func:`qcut` now returns a :class:`DatetimeIndex` or :class:`TimedeltaIndex` bins when the input is datetime or timedelta dtype respectively and ``retbins=True`` (:issue:`19891`)
528+
- :meth:`DatetimeIndex.to_period` and :meth:`Timestamp.to_period` will issue a warning when timezone information will be lost (:issue:`21333`)
528529

529530
.. _whatsnew_0240.api.other:
530531

@@ -626,6 +627,8 @@ Datetimelike
626627
- Bug in :class:`DataFrame` with mixed dtypes including ``datetime64[ns]`` incorrectly raising ``TypeError`` on equality comparisons (:issue:`13128`,:issue:`22163`)
627628
- Bug in :meth:`DataFrame.eq` comparison against ``NaT`` incorrectly returning ``True`` or ``NaN`` (:issue:`15697`,:issue:`22163`)
628629
- Bug in :class:`DatetimeIndex` subtraction that incorrectly failed to raise `OverflowError` (:issue:`22492`, :issue:`22508`)
630+
- Bug in :class:`DatetimeIndex` incorrectly allowing indexing with ``Timedelta`` object (:issue:`20464`)
631+
-
629632

630633
Timedelta
631634
^^^^^^^^^
@@ -634,7 +637,7 @@ Timedelta
634637
- Bug in multiplying a :class:`Series` with numeric dtype against a ``timedelta`` object (:issue:`22390`)
635638
- Bug in :class:`Series` with numeric dtype when adding or subtracting an an array or ``Series`` with ``timedelta64`` dtype (:issue:`22390`)
636639
- Bug in :class:`Index` with numeric dtype when multiplying or dividing an array with dtype ``timedelta64`` (:issue:`22390`)
637-
-
640+
- Bug in :class:`TimedeltaIndex` incorrectly allowing indexing with ``Timestamp`` object (:issue:`20464`)
638641
-
639642
-
640643

pandas/_libs/tslibs/timestamps.pyx

+6
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,12 @@ class Timestamp(_Timestamp):
737737
"""
738738
from pandas import Period
739739

740+
if self.tz is not None:
741+
# GH#21333
742+
warnings.warn("Converting to Period representation will "
743+
"drop timezone information.",
744+
UserWarning)
745+
740746
if freq is None:
741747
freq = self.freq
742748

pandas/core/indexes/datetimes.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from __future__ import division
33
import operator
44
import warnings
5-
from datetime import time, datetime
5+
from datetime import time, datetime, timedelta
66

77
import numpy as np
88
from pytz import utc
@@ -730,6 +730,10 @@ def to_period(self, freq=None):
730730
"""
731731
from pandas.core.indexes.period import PeriodIndex
732732

733+
if self.tz is not None:
734+
warnings.warn("Converting to PeriodIndex representation will "
735+
"drop timezone information.", UserWarning)
736+
733737
if freq is None:
734738
freq = self.freqstr or self.inferred_freq
735739

@@ -740,7 +744,7 @@ def to_period(self, freq=None):
740744

741745
freq = get_period_alias(freq)
742746

743-
return PeriodIndex(self.values, name=self.name, freq=freq, tz=self.tz)
747+
return PeriodIndex(self.values, name=self.name, freq=freq)
744748

745749
def snap(self, freq='S'):
746750
"""
@@ -1204,6 +1208,12 @@ def get_loc(self, key, method=None, tolerance=None):
12041208
key = Timestamp(key, tz=self.tz)
12051209
return Index.get_loc(self, key, method, tolerance)
12061210

1211+
elif isinstance(key, timedelta):
1212+
# GH#20464
1213+
raise TypeError("Cannot index {cls} with {other}"
1214+
.format(cls=type(self).__name__,
1215+
other=type(key).__name__))
1216+
12071217
if isinstance(key, time):
12081218
if method is not None:
12091219
raise NotImplementedError('cannot yet lookup inexact labels '

pandas/core/indexes/timedeltas.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
""" implement the TimedeltaIndex """
22
import operator
3+
from datetime import datetime
34

45
import numpy as np
56
from pandas.core.dtypes.common import (
@@ -487,7 +488,11 @@ def get_loc(self, key, method=None, tolerance=None):
487488
-------
488489
loc : int
489490
"""
490-
if is_list_like(key):
491+
if is_list_like(key) or (isinstance(key, datetime) and key is not NaT):
492+
# GH#20464 datetime check here is to ensure we don't allow
493+
# datetime objects to be incorrectly treated as timedelta
494+
# objects; NaT is a special case because it plays a double role
495+
# as Not-A-Timedelta
491496
raise TypeError
492497

493498
if isna(key):

pandas/tests/indexes/datetimes/test_astype.py

+17-5
Original file line numberDiff line numberDiff line change
@@ -246,15 +246,19 @@ def setup_method(self, method):
246246
def test_to_period_millisecond(self):
247247
index = self.index
248248

249-
period = index.to_period(freq='L')
249+
with tm.assert_produces_warning(UserWarning):
250+
# warning that timezone info will be lost
251+
period = index.to_period(freq='L')
250252
assert 2 == len(period)
251253
assert period[0] == Period('2007-01-01 10:11:12.123Z', 'L')
252254
assert period[1] == Period('2007-01-01 10:11:13.789Z', 'L')
253255

254256
def test_to_period_microsecond(self):
255257
index = self.index
256258

257-
period = index.to_period(freq='U')
259+
with tm.assert_produces_warning(UserWarning):
260+
# warning that timezone info will be lost
261+
period = index.to_period(freq='U')
258262
assert 2 == len(period)
259263
assert period[0] == Period('2007-01-01 10:11:12.123456Z', 'U')
260264
assert period[1] == Period('2007-01-01 10:11:13.789123Z', 'U')
@@ -264,12 +268,20 @@ def test_to_period_microsecond(self):
264268
dateutil.tz.tzutc()])
265269
def test_to_period_tz(self, tz):
266270
ts = date_range('1/1/2000', '2/1/2000', tz=tz)
267-
result = ts.to_period()[0]
268-
expected = ts[0].to_period()
271+
272+
with tm.assert_produces_warning(UserWarning):
273+
# GH#21333 warning that timezone info will be lost
274+
result = ts.to_period()[0]
275+
expected = ts[0].to_period()
276+
269277
assert result == expected
270278

271279
expected = date_range('1/1/2000', '2/1/2000').to_period()
272-
result = ts.to_period()
280+
281+
with tm.assert_produces_warning(UserWarning):
282+
# GH#21333 warning that timezone info will be lost
283+
result = ts.to_period()
284+
273285
tm.assert_index_equal(result, expected)
274286

275287
def test_to_period_nofreq(self):

pandas/tests/indexes/datetimes/test_indexing.py

+14
Original file line numberDiff line numberDiff line change
@@ -586,3 +586,17 @@ def test_reasonable_keyerror(self):
586586
with pytest.raises(KeyError) as excinfo:
587587
index.get_loc('1/1/2000')
588588
assert '2000' in str(excinfo.value)
589+
590+
@pytest.mark.parametrize('key', [pd.Timedelta(0),
591+
pd.Timedelta(1),
592+
timedelta(0)])
593+
def test_timedelta_invalid_key(self, key):
594+
# GH#20464
595+
dti = pd.date_range('1970-01-01', periods=10)
596+
with pytest.raises(TypeError):
597+
dti.get_loc(key)
598+
599+
def test_get_loc_nat(self):
600+
# GH#20464
601+
index = DatetimeIndex(['1/3/2000', 'NaT'])
602+
assert index.get_loc(pd.NaT) == 1

pandas/tests/indexes/timedeltas/test_indexing.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import timedelta
1+
from datetime import datetime, timedelta
22

33
import pytest
44
import numpy as np
@@ -41,6 +41,15 @@ def test_getitem(self):
4141
tm.assert_index_equal(result, expected)
4242
assert result.freq == expected.freq
4343

44+
@pytest.mark.parametrize('key', [pd.Timestamp('1970-01-01'),
45+
pd.Timestamp('1970-01-02'),
46+
datetime(1970, 1, 1)])
47+
def test_timestamp_invalid_key(self, key):
48+
# GH#20464
49+
tdi = pd.timedelta_range(0, periods=10)
50+
with pytest.raises(TypeError):
51+
tdi.get_loc(key)
52+
4453

4554
class TestWhere(object):
4655
# placeholder for symmetry with DatetimeIndex and PeriodIndex tests

pandas/tests/scalar/timestamp/test_timestamp.py

+8
Original file line numberDiff line numberDiff line change
@@ -929,3 +929,11 @@ def test_to_datetime_bijective(self):
929929
with tm.assert_produces_warning(exp_warning, check_stacklevel=False):
930930
assert (Timestamp(Timestamp.min.to_pydatetime()).value / 1000 ==
931931
Timestamp.min.value / 1000)
932+
933+
def test_to_period_tz_warning(self):
934+
# GH#21333 make sure a warning is issued when timezone
935+
# info is lost
936+
ts = Timestamp('2009-04-15 16:17:18', tz='US/Eastern')
937+
with tm.assert_produces_warning(UserWarning):
938+
# warning that timezone info will be lost
939+
ts.to_period('D')

0 commit comments

Comments
 (0)