From caa0a62c05e4a4dbf08899f72a4ef3f4e34c9730 Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Sat, 10 Nov 2018 16:17:03 -0800 Subject: [PATCH 1/9] DEPR: tz_convert in the Timestamp constructor --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/_libs/tslibs/timestamps.pyx | 8 +++++--- pandas/tests/scalar/timestamp/test_timestamp.py | 16 +++++++++++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index eb20d5368ef15..48d0e3084b361 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -967,6 +967,7 @@ Deprecations - The class ``FrozenNDArray`` has been deprecated. When unpickling, ``FrozenNDArray`` will be unpickled to ``np.ndarray`` once this class is removed (:issue:`9031`) - Deprecated the `nthreads` keyword of :func:`pandas.read_feather` in favor of `use_threads` to reflect the changes in pyarrow 0.11.0. (:issue:`23053`) +- Converting a tz-aware :class:`Timestamp` with :class:`Timestamp` and the ``tz`` argument is now deprecated. Instead use :meth:`Timestamp.tz_convert` (:issue:`23579`) .. _whatsnew_0240.deprecations.datetimelike_int_ops: diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index e2914957d01cd..28ab6ddb4ba84 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -709,9 +709,6 @@ class Timestamp(_Timestamp): elif ts_input is _no_input: # User passed keyword arguments. - if tz is None: - # Handle the case where the user passes `tz` and not `tzinfo` - tz = tzinfo return Timestamp(datetime(year, month, day, hour or 0, minute or 0, second or 0, microsecond or 0, tzinfo), @@ -728,6 +725,11 @@ class Timestamp(_Timestamp): # User passed tzinfo instead of tz; avoid silently ignoring tz, tzinfo = tzinfo, None + if getattr(ts_input, 'tzinfo', None) is not None and tz is not None: + warnings.warn("Passing a datetime or Timestamp with tzinfo and the" + " tz parameter will raise in the future. Use" + " tz_convert instead.", FutureWarning) + ts = convert_to_tsobject(ts_input, tz, unit, 0, 0, nanosecond or 0) if ts.value == NPY_NAT: diff --git a/pandas/tests/scalar/timestamp/test_timestamp.py b/pandas/tests/scalar/timestamp/test_timestamp.py index 7af0b281aeaa5..9e6dfc7753bb2 100644 --- a/pandas/tests/scalar/timestamp/test_timestamp.py +++ b/pandas/tests/scalar/timestamp/test_timestamp.py @@ -244,7 +244,10 @@ def test_constructor(self): assert conversion.pydt_to_i8(result) == expected_tz # should convert to UTC - result = Timestamp(result, tz='UTC') + if tz is not None: + result = Timestamp(result).tz_convert('UTC') + else: + result = Timestamp(result, tz='UTC') expected_utc = expected - offset * 3600 * 1000000000 assert result.value == expected_utc assert conversion.pydt_to_i8(result) == expected_utc @@ -295,7 +298,7 @@ def test_constructor_with_stringoffset(self): assert conversion.pydt_to_i8(result) == expected_tz # should convert to UTC - result = Timestamp(result, tz='UTC') + result = Timestamp(result).tz_convert('UTC') expected_utc = expected assert result.value == expected_utc assert conversion.pydt_to_i8(result) == expected_utc @@ -558,7 +561,7 @@ def test_construct_timestamp_near_dst(self, offset): # GH 20854 expected = Timestamp('2016-10-30 03:00:00{}'.format(offset), tz='Europe/Helsinki') - result = Timestamp(expected, tz='Europe/Helsinki') + result = Timestamp(expected).tz_convert('Europe/Helsinki') assert result == expected @pytest.mark.parametrize('arg', [ @@ -580,6 +583,13 @@ def test_constructor_invalid_frequency(self): with tm.assert_raises_regex(ValueError, "Invalid frequency:"): Timestamp('2012-01-01', freq=[]) + @pytest.mark.parametrize('box', [datetime, Timestamp]) + def test_depreciate_tz_and_tzinfo_in_datetime_input(self, box): + # GH 23579 + kwargs = {'year': 2018, 'month': 1, 'day': 1, 'tzinfo': utc} + with tm.assert_produces_warning(FutureWarning): + Timestamp(box(**kwargs), tz='US/Pacific') + class TestTimestamp(object): From 7c942265ce25869fbf2de9744463c2e8767eacd3 Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Sat, 10 Nov 2018 16:19:48 -0800 Subject: [PATCH 2/9] clarify whatsnew --- doc/source/whatsnew/v0.24.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 48d0e3084b361..4b2ddafc2b3aa 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -967,7 +967,7 @@ Deprecations - The class ``FrozenNDArray`` has been deprecated. When unpickling, ``FrozenNDArray`` will be unpickled to ``np.ndarray`` once this class is removed (:issue:`9031`) - Deprecated the `nthreads` keyword of :func:`pandas.read_feather` in favor of `use_threads` to reflect the changes in pyarrow 0.11.0. (:issue:`23053`) -- Converting a tz-aware :class:`Timestamp` with :class:`Timestamp` and the ``tz`` argument is now deprecated. Instead use :meth:`Timestamp.tz_convert` (:issue:`23579`) +- 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`) .. _whatsnew_0240.deprecations.datetimelike_int_ops: From 2d138089fe1f677feddd5038837b0fc9d96fe366 Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Sat, 10 Nov 2018 16:21:59 -0800 Subject: [PATCH 3/9] Add more backticks --- doc/source/whatsnew/v0.24.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 4b2ddafc2b3aa..b3ef255bc8ef0 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -967,7 +967,7 @@ Deprecations - The class ``FrozenNDArray`` has been deprecated. When unpickling, ``FrozenNDArray`` will be unpickled to ``np.ndarray`` once this class is removed (:issue:`9031`) - Deprecated the `nthreads` keyword of :func:`pandas.read_feather` in favor of `use_threads` to reflect the changes in pyarrow 0.11.0. (:issue:`23053`) -- 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`) +- 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`) .. _whatsnew_0240.deprecations.datetimelike_int_ops: From 795aa1982ac0e349e2827a79c3947225ef93d43f Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Sat, 10 Nov 2018 17:47:04 -0800 Subject: [PATCH 4/9] add comma after instead, fix test --- doc/source/whatsnew/v0.24.0.txt | 2 +- pandas/_libs/tslibs/timestamps.pyx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index b3ef255bc8ef0..83f79c59f2876 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -967,7 +967,7 @@ Deprecations - The class ``FrozenNDArray`` has been deprecated. When unpickling, ``FrozenNDArray`` will be unpickled to ``np.ndarray`` once this class is removed (:issue:`9031`) - Deprecated the `nthreads` keyword of :func:`pandas.read_feather` in favor of `use_threads` to reflect the changes in pyarrow 0.11.0. (:issue:`23053`) -- 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`) +- 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`) .. _whatsnew_0240.deprecations.datetimelike_int_ops: diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 28ab6ddb4ba84..3190ae05712e9 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -719,7 +719,7 @@ class Timestamp(_Timestamp): # microsecond[, nanosecond[, tzinfo]]]]]]) return Timestamp(datetime(ts_input, freq, tz, unit or 0, year or 0, month or 0, day or 0, - minute), nanosecond=hour, tz=minute) + minute), nanosecond=hour) if tzinfo is not None: # User passed tzinfo instead of tz; avoid silently ignoring From 22cf8a5788df01f97532fd2a9bc5535716bd3ed7 Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Sun, 11 Nov 2018 22:01:17 -0800 Subject: [PATCH 5/9] refactor constructor with args --- pandas/_libs/tslibs/timestamps.pyx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 3190ae05712e9..a6d186acf2803 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -700,6 +700,9 @@ class Timestamp(_Timestamp): elif tz is not None: raise ValueError('Can provide at most one of tz, tzinfo') + # User passed tzinfo instead of tz; avoid silently ignoring + tz, tzinfo = tzinfo, None + if is_string_object(ts_input): # User passed a date string to parse. # Check that the user didn't also pass a date attribute kwarg. @@ -709,21 +712,18 @@ class Timestamp(_Timestamp): elif ts_input is _no_input: # User passed keyword arguments. - return Timestamp(datetime(year, month, day, hour or 0, - minute or 0, second or 0, - microsecond or 0, tzinfo), - nanosecond=nanosecond, tz=tz) + ts_input = datetime(year, month, day, hour or 0, + minute or 0, second or 0, + microsecond or 0) elif is_integer_object(freq): # User passed positional arguments: # Timestamp(year, month, day[, hour[, minute[, second[, # microsecond[, nanosecond[, tzinfo]]]]]]) - return Timestamp(datetime(ts_input, freq, tz, unit or 0, - year or 0, month or 0, day or 0, - minute), nanosecond=hour) - - if tzinfo is not None: - # User passed tzinfo instead of tz; avoid silently ignoring - tz, tzinfo = tzinfo, None + ts_input = datetime(ts_input, freq, tz, unit or 0, + year or 0, month or 0, day or 0) + nanosecond = hour + tz = minute + freq = None if getattr(ts_input, 'tzinfo', None) is not None and tz is not None: warnings.warn("Passing a datetime or Timestamp with tzinfo and the" From 4db92acc80fa0ed2b8425eb5d05a04b51c56d8c4 Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Mon, 12 Nov 2018 15:20:28 -0800 Subject: [PATCH 6/9] add test for dti --- pandas/tests/indexes/datetimes/test_construction.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pandas/tests/indexes/datetimes/test_construction.py b/pandas/tests/indexes/datetimes/test_construction.py index 42a75f277faa6..4b2c07af6af68 100644 --- a/pandas/tests/indexes/datetimes/test_construction.py +++ b/pandas/tests/indexes/datetimes/test_construction.py @@ -521,6 +521,12 @@ def test_construction_from_replaced_timestamps_with_dst(self): tz='Australia/Melbourne') tm.assert_index_equal(result, expected) + def test_construction_with_tz_and_tz_aware_dti(self): + # GH 23579 + dti = date_range('2016-01-01', periods=3, tz='US/Central') + with pytest.raises(TypeError): + DatetimeIndex(dti, tz='Asia/Tokyo') + class TestTimeSeries(object): From 656beffd3cf201310e13423e60e21a0197b1acba Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Wed, 14 Nov 2018 15:53:19 -0800 Subject: [PATCH 7/9] Handle warnings --- pandas/core/indexes/datetimes.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index a1fed8b93fcbb..5a1484340738a 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -967,7 +967,10 @@ def get_loc(self, key, method=None, tolerance=None): if isinstance(key, datetime): # needed to localize naive datetimes - key = Timestamp(key, tz=self.tz) + if key.tzinfo is None: + key = Timestamp(key, tz=self.tz) + else: + key = Timestamp(key).tz_convert(self.tz) return Index.get_loc(self, key, method, tolerance) elif isinstance(key, timedelta): From 3a3b07cc87a600cae5ae143d864d26f0916c868c Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Thu, 15 Nov 2018 22:22:02 -0800 Subject: [PATCH 8/9] address some warnings? --- pandas/core/arrays/datetimes.py | 7 ++++++- pandas/core/generic.py | 8 +++++++- pandas/io/formats/format.py | 5 ++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index bacbece718e9a..31d2e06408566 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -45,7 +45,12 @@ def _to_m8(key, tz=None): """ if not isinstance(key, Timestamp): # this also converts strings - key = Timestamp(key, tz=tz) + key = Timestamp(key) + if key.tz is not None and tz is not None: + # Don't tz_localize(None) if key is already tz-aware + key = key.tz_convert(tz) + else: + key = key.tz_localize(tz) return np.int64(conversion.pydt_to_i8(key)).view(_NS_DTYPE) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index a1d60c493dda0..e5d1b29324926 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -9336,8 +9336,14 @@ def describe_categorical_1d(data): if is_datetime64_any_dtype(data): tz = data.dt.tz asint = data.dropna().values.view('i8') + top = Timestamp(top) + if top.tz is not None and tz is not None: + # Don't tz_localize(None) if key is already tz-aware + top = top.tz_convert(tz) + else: + top = top.tz_localize(tz) names += ['top', 'freq', 'first', 'last'] - result += [Timestamp(top, tz=tz), freq, + result += [top, freq, Timestamp(asint.min(), tz=tz), Timestamp(asint.max(), tz=tz)] else: diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py index b63e44c6c3437..c777b89eeaf12 100644 --- a/pandas/io/formats/format.py +++ b/pandas/io/formats/format.py @@ -1246,7 +1246,10 @@ def _format_datetime64(x, tz=None, nat_rep='NaT'): return nat_rep if tz is not None or not isinstance(x, Timestamp): - x = Timestamp(x, tz=tz) + if getattr(x, 'tzinfo', None) is not None: + x = Timestamp(x).tz_convert(tz) + else: + x = Timestamp(x).tz_localize(tz) return str(x) From aaec019218b09ed60476b7519a50448069ee1155 Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Fri, 16 Nov 2018 13:01:58 -0800 Subject: [PATCH 9/9] Fix more tz kwarg usage --- pandas/core/indexes/datetimes.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index d1d1814d48527..bc79e5d12643b 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -937,7 +937,10 @@ def get_value(self, series, key): # needed to localize naive datetimes if self.tz is not None: - key = Timestamp(key, tz=self.tz) + if key.tzinfo is not None: + key = Timestamp(key).tz_convert(self.tz) + else: + key = Timestamp(key).tz_localize(self.tz) return self.get_value_maybe_box(series, key) @@ -963,7 +966,11 @@ def get_value(self, series, key): def get_value_maybe_box(self, series, key): # needed to localize naive datetimes if self.tz is not None: - key = Timestamp(key, tz=self.tz) + key = Timestamp(key) + if key.tzinfo is not None: + key = key.tz_convert(self.tz) + else: + key = key.tz_localize(self.tz) elif not isinstance(key, Timestamp): key = Timestamp(key) values = self._engine.get_value(com.values_from_object(series), @@ -1013,7 +1020,11 @@ def get_loc(self, key, method=None, tolerance=None): pass try: - stamp = Timestamp(key, tz=self.tz) + stamp = Timestamp(key) + if stamp.tzinfo is not None and self.tz is not None: + stamp = stamp.tz_convert(self.tz) + else: + stamp = stamp.tz_localize(self.tz) return Index.get_loc(self, stamp, method, tolerance) except KeyError: raise KeyError(key)