Skip to content

Commit 06075a9

Browse files
mroeschkePingviinituutti
authored andcommitted
DEPR: tz_convert in the Timestamp constructor (pandas-dev#23621)
1 parent 1ffd5f4 commit 06075a9

File tree

8 files changed

+71
-24
lines changed

8 files changed

+71
-24
lines changed

doc/source/whatsnew/v0.24.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,7 @@ Deprecations
10361036
- Constructing a :class:`TimedeltaIndex` from data with ``datetime64``-dtyped data is deprecated, will raise ``TypeError`` in a future version (:issue:`23539`)
10371037
- The ``keep_tz=False`` option (the default) of the ``keep_tz`` keyword of
10381038
:meth:`DatetimeIndex.to_series` is deprecated (:issue:`17832`).
1039+
- Timezone converting a tz-aware ``datetime.datetime`` or :class:`Timestamp` with :class:`Timestamp` and the ``tz`` argument is now deprecated. Instead, use :meth:`Timestamp.tz_convert` (:issue:`23579`)
10391040

10401041
.. _whatsnew_0240.deprecations.datetimelike_int_ops:
10411042

pandas/_libs/tslibs/timestamps.pyx

+16-14
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,9 @@ class Timestamp(_Timestamp):
701701
elif tz is not None:
702702
raise ValueError('Can provide at most one of tz, tzinfo')
703703

704+
# User passed tzinfo instead of tz; avoid silently ignoring
705+
tz, tzinfo = tzinfo, None
706+
704707
if is_string_object(ts_input):
705708
# User passed a date string to parse.
706709
# Check that the user didn't also pass a date attribute kwarg.
@@ -710,24 +713,23 @@ class Timestamp(_Timestamp):
710713

711714
elif ts_input is _no_input:
712715
# User passed keyword arguments.
713-
if tz is None:
714-
# Handle the case where the user passes `tz` and not `tzinfo`
715-
tz = tzinfo
716-
return Timestamp(datetime(year, month, day, hour or 0,
717-
minute or 0, second or 0,
718-
microsecond or 0, tzinfo),
719-
nanosecond=nanosecond, tz=tz)
716+
ts_input = datetime(year, month, day, hour or 0,
717+
minute or 0, second or 0,
718+
microsecond or 0)
720719
elif is_integer_object(freq):
721720
# User passed positional arguments:
722721
# Timestamp(year, month, day[, hour[, minute[, second[,
723722
# microsecond[, nanosecond[, tzinfo]]]]]])
724-
return Timestamp(datetime(ts_input, freq, tz, unit or 0,
725-
year or 0, month or 0, day or 0,
726-
minute), nanosecond=hour, tz=minute)
727-
728-
if tzinfo is not None:
729-
# User passed tzinfo instead of tz; avoid silently ignoring
730-
tz, tzinfo = tzinfo, None
723+
ts_input = datetime(ts_input, freq, tz, unit or 0,
724+
year or 0, month or 0, day or 0)
725+
nanosecond = hour
726+
tz = minute
727+
freq = None
728+
729+
if getattr(ts_input, 'tzinfo', None) is not None and tz is not None:
730+
warnings.warn("Passing a datetime or Timestamp with tzinfo and the"
731+
" tz parameter will raise in the future. Use"
732+
" tz_convert instead.", FutureWarning)
731733

732734
ts = convert_to_tsobject(ts_input, tz, unit, 0, 0, nanosecond or 0)
733735

pandas/core/arrays/datetimes.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,12 @@ def _to_m8(key, tz=None):
4545
"""
4646
if not isinstance(key, Timestamp):
4747
# this also converts strings
48-
key = Timestamp(key, tz=tz)
48+
key = Timestamp(key)
49+
if key.tzinfo is not None and tz is not None:
50+
# Don't tz_localize(None) if key is already tz-aware
51+
key = key.tz_convert(tz)
52+
else:
53+
key = key.tz_localize(tz)
4954

5055
return np.int64(conversion.pydt_to_i8(key)).view(_NS_DTYPE)
5156

pandas/core/generic.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -9336,8 +9336,14 @@ def describe_categorical_1d(data):
93369336
if is_datetime64_any_dtype(data):
93379337
tz = data.dt.tz
93389338
asint = data.dropna().values.view('i8')
9339+
top = Timestamp(top)
9340+
if top.tzinfo is not None and tz is not None:
9341+
# Don't tz_localize(None) if key is already tz-aware
9342+
top = top.tz_convert(tz)
9343+
else:
9344+
top = top.tz_localize(tz)
93399345
names += ['top', 'freq', 'first', 'last']
9340-
result += [Timestamp(top, tz=tz), freq,
9346+
result += [top, freq,
93419347
Timestamp(asint.min(), tz=tz),
93429348
Timestamp(asint.max(), tz=tz)]
93439349
else:

pandas/core/indexes/datetimes.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,10 @@ def get_value(self, series, key):
937937

938938
# needed to localize naive datetimes
939939
if self.tz is not None:
940-
key = Timestamp(key, tz=self.tz)
940+
if key.tzinfo is not None:
941+
key = Timestamp(key).tz_convert(self.tz)
942+
else:
943+
key = Timestamp(key).tz_localize(self.tz)
941944

942945
return self.get_value_maybe_box(series, key)
943946

@@ -963,7 +966,11 @@ def get_value(self, series, key):
963966
def get_value_maybe_box(self, series, key):
964967
# needed to localize naive datetimes
965968
if self.tz is not None:
966-
key = Timestamp(key, tz=self.tz)
969+
key = Timestamp(key)
970+
if key.tzinfo is not None:
971+
key = key.tz_convert(self.tz)
972+
else:
973+
key = key.tz_localize(self.tz)
967974
elif not isinstance(key, Timestamp):
968975
key = Timestamp(key)
969976
values = self._engine.get_value(com.values_from_object(series),
@@ -986,7 +993,10 @@ def get_loc(self, key, method=None, tolerance=None):
986993

987994
if isinstance(key, datetime):
988995
# needed to localize naive datetimes
989-
key = Timestamp(key, tz=self.tz)
996+
if key.tzinfo is None:
997+
key = Timestamp(key, tz=self.tz)
998+
else:
999+
key = Timestamp(key).tz_convert(self.tz)
9901000
return Index.get_loc(self, key, method, tolerance)
9911001

9921002
elif isinstance(key, timedelta):
@@ -1010,7 +1020,11 @@ def get_loc(self, key, method=None, tolerance=None):
10101020
pass
10111021

10121022
try:
1013-
stamp = Timestamp(key, tz=self.tz)
1023+
stamp = Timestamp(key)
1024+
if stamp.tzinfo is not None and self.tz is not None:
1025+
stamp = stamp.tz_convert(self.tz)
1026+
else:
1027+
stamp = stamp.tz_localize(self.tz)
10141028
return Index.get_loc(self, stamp, method, tolerance)
10151029
except KeyError:
10161030
raise KeyError(key)

pandas/io/formats/format.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1246,7 +1246,10 @@ def _format_datetime64(x, tz=None, nat_rep='NaT'):
12461246
return nat_rep
12471247

12481248
if tz is not None or not isinstance(x, Timestamp):
1249-
x = Timestamp(x, tz=tz)
1249+
if getattr(x, 'tzinfo', None) is not None:
1250+
x = Timestamp(x).tz_convert(tz)
1251+
else:
1252+
x = Timestamp(x).tz_localize(tz)
12501253

12511254
return str(x)
12521255

pandas/tests/indexes/datetimes/test_construction.py

+6
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,12 @@ def test_construction_from_replaced_timestamps_with_dst(self):
521521
tz='Australia/Melbourne')
522522
tm.assert_index_equal(result, expected)
523523

524+
def test_construction_with_tz_and_tz_aware_dti(self):
525+
# GH 23579
526+
dti = date_range('2016-01-01', periods=3, tz='US/Central')
527+
with pytest.raises(TypeError):
528+
DatetimeIndex(dti, tz='Asia/Tokyo')
529+
524530

525531
class TestTimeSeries(object):
526532

pandas/tests/scalar/timestamp/test_timestamp.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,10 @@ def test_constructor(self):
244244
assert conversion.pydt_to_i8(result) == expected_tz
245245

246246
# should convert to UTC
247-
result = Timestamp(result, tz='UTC')
247+
if tz is not None:
248+
result = Timestamp(result).tz_convert('UTC')
249+
else:
250+
result = Timestamp(result, tz='UTC')
248251
expected_utc = expected - offset * 3600 * 1000000000
249252
assert result.value == expected_utc
250253
assert conversion.pydt_to_i8(result) == expected_utc
@@ -295,7 +298,7 @@ def test_constructor_with_stringoffset(self):
295298
assert conversion.pydt_to_i8(result) == expected_tz
296299

297300
# should convert to UTC
298-
result = Timestamp(result, tz='UTC')
301+
result = Timestamp(result).tz_convert('UTC')
299302
expected_utc = expected
300303
assert result.value == expected_utc
301304
assert conversion.pydt_to_i8(result) == expected_utc
@@ -558,7 +561,7 @@ def test_construct_timestamp_near_dst(self, offset):
558561
# GH 20854
559562
expected = Timestamp('2016-10-30 03:00:00{}'.format(offset),
560563
tz='Europe/Helsinki')
561-
result = Timestamp(expected, tz='Europe/Helsinki')
564+
result = Timestamp(expected).tz_convert('Europe/Helsinki')
562565
assert result == expected
563566

564567
@pytest.mark.parametrize('arg', [
@@ -580,6 +583,13 @@ def test_constructor_invalid_frequency(self):
580583
with pytest.raises(ValueError, match="Invalid frequency:"):
581584
Timestamp('2012-01-01', freq=[])
582585

586+
@pytest.mark.parametrize('box', [datetime, Timestamp])
587+
def test_depreciate_tz_and_tzinfo_in_datetime_input(self, box):
588+
# GH 23579
589+
kwargs = {'year': 2018, 'month': 1, 'day': 1, 'tzinfo': utc}
590+
with tm.assert_produces_warning(FutureWarning):
591+
Timestamp(box(**kwargs), tz='US/Pacific')
592+
583593

584594
class TestTimestamp(object):
585595

0 commit comments

Comments
 (0)