diff --git a/asv_bench/benchmarks/tslibs/tz_convert.py b/asv_bench/benchmarks/tslibs/tz_convert.py index 2a1f559bdf6d4..c2c90024ca5bd 100644 --- a/asv_bench/benchmarks/tslibs/tz_convert.py +++ b/asv_bench/benchmarks/tslibs/tz_convert.py @@ -1,16 +1,23 @@ import numpy as np from pytz import UTC -from pandas._libs.tslibs.tzconversion import tz_convert, tz_localize_to_utc +from pandas._libs.tslibs.tzconversion import tz_localize_to_utc from .tslib import _sizes, _tzs +try: + old_sig = False + from pandas._libs.tslibs.tzconversion import tz_convert_from_utc +except ImportError: + old_sig = True + from pandas._libs.tslibs.tzconversion import tz_convert as tz_convert_from_utc + class TimeTZConvert: - params = ( + params = [ _sizes, [x for x in _tzs if x is not None], - ) + ] param_names = ["size", "tz"] def setup(self, size, tz): @@ -21,7 +28,13 @@ def time_tz_convert_from_utc(self, size, tz): # effectively: # dti = DatetimeIndex(self.i8data, tz=tz) # dti.tz_localize(None) - tz_convert(self.i8data, UTC, tz) + if size >= 10 ** 6 and str(tz) == "tzlocal()": + # asv fill will because each call takes 8+seconds + return + if old_sig: + tz_convert_from_utc(self.i8data, UTC, tz) + else: + tz_convert_from_utc(self.i8data, tz) def time_tz_localize_to_utc(self, size, tz): # effectively: diff --git a/pandas/_libs/tslibs/__init__.py b/pandas/_libs/tslibs/__init__.py index c2f3478a50ab4..6fe6fa0a13c34 100644 --- a/pandas/_libs/tslibs/__init__.py +++ b/pandas/_libs/tslibs/__init__.py @@ -19,7 +19,7 @@ "ints_to_pytimedelta", "get_resolution", "Timestamp", - "tz_convert_single", + "tz_convert_from_utc_single", "to_offset", "Tick", "BaseOffset", @@ -34,7 +34,7 @@ from .resolution import Resolution from .timedeltas import Timedelta, delta_to_nanoseconds, ints_to_pytimedelta from .timestamps import Timestamp -from .tzconversion import tz_convert_single +from .tzconversion import tz_convert_from_utc_single from .vectorized import ( dt64arr_to_periodarr, get_resolution, diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index fb07e3fe7547e..0f9280ae92d39 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -46,8 +46,7 @@ from pandas._libs.tslibs.np_datetime cimport ( dt64_to_dtstruct, pydate_to_dtstruct, ) -from pandas._libs.tslibs.timezones cimport utc_pytz as UTC -from pandas._libs.tslibs.tzconversion cimport tz_convert_single +from pandas._libs.tslibs.tzconversion cimport tz_convert_from_utc_single from .dtypes cimport PeriodDtypeCode from .timedeltas cimport delta_to_nanoseconds @@ -264,7 +263,7 @@ cdef _to_dt64D(dt): # equiv `Timestamp(dt).value` or `dt.timestamp() * 10**9` nanos = getattr(dt, "nanosecond", 0) i8 = convert_datetime_to_tsobject(dt, tz=None, nanos=nanos).value - dt = tz_convert_single(i8, UTC, dt.tzinfo) + dt = tz_convert_from_utc_single(i8, dt.tzinfo) dt = np.int64(dt).astype('datetime64[ns]') else: dt = np.datetime64(dt) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index a2dacd9d36b14..8cef685933863 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -59,7 +59,7 @@ from pandas._libs.tslibs.timezones cimport ( get_timezone, tz_compare, ) from pandas._libs.tslibs.tzconversion cimport ( - tz_convert_single, + tz_convert_from_utc_single, tz_localize_to_utc_single, ) @@ -1309,7 +1309,7 @@ default 'raise' else: if tz is None: # reset tz - value = tz_convert_single(self.value, UTC, self.tz) + value = tz_convert_from_utc_single(self.value, self.tz) return Timestamp(value, tz=tz, freq=self.freq) else: raise TypeError( @@ -1391,7 +1391,7 @@ default 'raise' tzobj = self.tzinfo value = self.value if tzobj is not None: - value = tz_convert_single(value, UTC, tzobj) + value = tz_convert_from_utc_single(value, tzobj) # setup components dt64_to_dtstruct(value, &dts) diff --git a/pandas/_libs/tslibs/tzconversion.pxd b/pandas/_libs/tslibs/tzconversion.pxd index 7d102868256de..1990afd77a8fb 100644 --- a/pandas/_libs/tslibs/tzconversion.pxd +++ b/pandas/_libs/tslibs/tzconversion.pxd @@ -3,7 +3,7 @@ from numpy cimport int64_t cdef int64_t tz_convert_utc_to_tzlocal(int64_t utc_val, tzinfo tz, bint* fold=*) -cpdef int64_t tz_convert_single(int64_t val, tzinfo tz1, tzinfo tz2) +cpdef int64_t tz_convert_from_utc_single(int64_t val, tzinfo tz) cdef int64_t tz_localize_to_utc_single( int64_t val, tzinfo tz, object ambiguous=*, object nonexistent=* ) except? -1 diff --git a/pandas/_libs/tslibs/tzconversion.pyx b/pandas/_libs/tslibs/tzconversion.pyx index 98c40e109dbab..a6afd47d93479 100644 --- a/pandas/_libs/tslibs/tzconversion.pyx +++ b/pandas/_libs/tslibs/tzconversion.pyx @@ -366,17 +366,16 @@ cdef int64_t tz_convert_utc_to_tzlocal(int64_t utc_val, tzinfo tz, bint* fold=NU return _tz_convert_tzlocal_utc(utc_val, tz, to_utc=False, fold=fold) -cpdef int64_t tz_convert_single(int64_t val, tzinfo tz1, tzinfo tz2): +cpdef int64_t tz_convert_from_utc_single(int64_t val, tzinfo tz): """ - Convert the val (in i8) from timezone1 to timezone2 + Convert the val (in i8) from UTC to tz - This is a single timezone version of tz_convert + This is a single value version of tz_convert_from_utc. Parameters ---------- val : int64 - tz1 : tzinfo - tz2 : tzinfo + tz : tzinfo Returns ------- @@ -384,38 +383,27 @@ cpdef int64_t tz_convert_single(int64_t val, tzinfo tz1, tzinfo tz2): """ cdef: int64_t arr[1] - bint to_utc = is_utc(tz2) - tzinfo tz - - # See GH#17734 We should always be converting either from UTC or to UTC - assert is_utc(tz1) or to_utc if val == NPY_NAT: return val - if to_utc: - tz = tz1 - else: - tz = tz2 - if is_utc(tz): return val elif is_tzlocal(tz): - return _tz_convert_tzlocal_utc(val, tz, to_utc=to_utc) + return _tz_convert_tzlocal_utc(val, tz, to_utc=False) else: arr[0] = val - return _tz_convert_dst(arr, tz, to_utc=to_utc)[0] + return _tz_convert_dst(arr, tz)[0] -def tz_convert(int64_t[:] vals, tzinfo tz1, tzinfo tz2): +def tz_convert_from_utc(int64_t[:] vals, tzinfo tz): """ - Convert the values (in i8) from timezone1 to timezone2 + Convert the values (in i8) from UTC to tz Parameters ---------- vals : int64 ndarray - tz1 : tzinfo - tz2 : tzinfo + tz : tzinfo Returns ------- @@ -423,36 +411,24 @@ def tz_convert(int64_t[:] vals, tzinfo tz1, tzinfo tz2): """ cdef: int64_t[:] converted - bint to_utc = is_utc(tz2) - tzinfo tz - - # See GH#17734 We should always be converting from UTC; otherwise - # should use tz_localize_to_utc. - assert is_utc(tz1) if len(vals) == 0: return np.array([], dtype=np.int64) - if to_utc: - tz = tz1 - else: - tz = tz2 - - converted = _tz_convert_one_way(vals, tz, to_utc=to_utc) + converted = _tz_convert_from_utc(vals, tz) return np.array(converted, dtype=np.int64) @cython.boundscheck(False) @cython.wraparound(False) -cdef int64_t[:] _tz_convert_one_way(int64_t[:] vals, tzinfo tz, bint to_utc): +cdef int64_t[:] _tz_convert_from_utc(int64_t[:] vals, tzinfo tz): """ Convert the given values (in i8) either to UTC or from UTC. Parameters ---------- vals : int64 ndarray - tz1 : tzinfo - to_utc : bool + tz : tzinfo Returns ------- @@ -472,9 +448,9 @@ cdef int64_t[:] _tz_convert_one_way(int64_t[:] vals, tzinfo tz, bint to_utc): if val == NPY_NAT: converted[i] = NPY_NAT else: - converted[i] = _tz_convert_tzlocal_utc(val, tz, to_utc) + converted[i] = _tz_convert_tzlocal_utc(val, tz, to_utc=False) else: - converted = _tz_convert_dst(vals, tz, to_utc) + converted = _tz_convert_dst(vals, tz) return converted @@ -565,9 +541,7 @@ cdef int64_t _tz_convert_tzlocal_utc(int64_t val, tzinfo tz, bint to_utc=True, @cython.boundscheck(False) @cython.wraparound(False) -cdef int64_t[:] _tz_convert_dst( - const int64_t[:] values, tzinfo tz, bint to_utc=True, -): +cdef int64_t[:] _tz_convert_dst(const int64_t[:] values, tzinfo tz): """ tz_convert for non-UTC non-tzlocal cases where we have to check DST transitions pointwise. @@ -576,8 +550,6 @@ cdef int64_t[:] _tz_convert_dst( ---------- values : ndarray[int64_t] tz : tzinfo - to_utc : bool - True if converting _to_ UTC, False if converting _from_ utc Returns ------- @@ -607,10 +579,7 @@ cdef int64_t[:] _tz_convert_dst( if v == NPY_NAT: result[i] = v else: - if to_utc: - result[i] = v - delta - else: - result[i] = v + delta + result[i] = v + delta else: # Previously, this search was done pointwise to try and benefit @@ -629,9 +598,6 @@ cdef int64_t[:] _tz_convert_dst( # it elsewhere? raise ValueError("First time before start of DST info") - if to_utc: - result[i] = v - deltas[pos[i]] - else: - result[i] = v + deltas[pos[i]] + result[i] = v + deltas[pos[i]] return result diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 8eac45cdedaec..5038df85c9160 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -728,7 +728,7 @@ def _local_timestamps(self): This is used to calculate time-of-day information as if the timestamps were timezone-naive. """ - return tzconversion.tz_convert(self.asi8, timezones.UTC, self.tz) + return tzconversion.tz_convert_from_utc(self.asi8, self.tz) def tz_convert(self, tz): """ @@ -960,7 +960,7 @@ def tz_localize(self, tz, ambiguous="raise", nonexistent="raise"): if self.tz is not None: if tz is None: - new_dates = tzconversion.tz_convert(self.asi8, timezones.UTC, self.tz) + new_dates = tzconversion.tz_convert_from_utc(self.asi8, self.tz) else: raise TypeError("Already tz-aware, use tz_convert to convert.") else: diff --git a/pandas/tests/scalar/timestamp/test_timezones.py b/pandas/tests/scalar/timestamp/test_timezones.py index 9611c827be6fe..83764aa184392 100644 --- a/pandas/tests/scalar/timestamp/test_timezones.py +++ b/pandas/tests/scalar/timestamp/test_timezones.py @@ -334,7 +334,7 @@ def test_timestamp_to_datetime_tzoffset(self): def test_timestamp_constructor_near_dst_boundary(self): # GH#11481 & GH#15777 # Naive string timestamps were being localized incorrectly - # with tz_convert_single instead of tz_localize_to_utc + # with tz_convert_from_utc_single instead of tz_localize_to_utc for tz in ["Europe/Brussels", "Europe/Prague"]: result = Timestamp("2015-10-25 01:00", tz=tz) diff --git a/pandas/tests/tslibs/test_api.py b/pandas/tests/tslibs/test_api.py index 957706fcb460e..ccaceb7e6f906 100644 --- a/pandas/tests/tslibs/test_api.py +++ b/pandas/tests/tslibs/test_api.py @@ -47,7 +47,7 @@ def test_namespace(): "delta_to_nanoseconds", "ints_to_pytimedelta", "localize_pydatetime", - "tz_convert_single", + "tz_convert_from_utc_single", "to_offset", ] diff --git a/pandas/tests/tslibs/test_conversion.py b/pandas/tests/tslibs/test_conversion.py index 5a16fea47e90d..b35940c6bb95b 100644 --- a/pandas/tests/tslibs/test_conversion.py +++ b/pandas/tests/tslibs/test_conversion.py @@ -12,9 +12,9 @@ def _compare_utc_to_local(tz_didx): def f(x): - return tzconversion.tz_convert_single(x, UTC, tz_didx.tz) + return tzconversion.tz_convert_from_utc_single(x, tz_didx.tz) - result = tzconversion.tz_convert(tz_didx.asi8, UTC, tz_didx.tz) + result = tzconversion.tz_convert_from_utc(tz_didx.asi8, tz_didx.tz) expected = np.vectorize(f)(tz_didx.asi8) tm.assert_numpy_array_equal(result, expected) @@ -22,9 +22,6 @@ def f(x): def _compare_local_to_utc(tz_didx, naive_didx): # Check that tz_localize behaves the same vectorized and pointwise. - def f(x): - return tzconversion.tz_convert_single(x, tz_didx.tz, UTC) - err1 = err2 = None try: result = tzconversion.tz_localize_to_utc(naive_didx.asi8, tz_didx.tz) @@ -71,7 +68,7 @@ def test_tz_convert_single_matches_tz_convert(tz_aware_fixture, freq): ], ) def test_tz_convert_corner(arr): - result = tzconversion.tz_convert(arr, UTC, timezones.maybe_get_tz("Asia/Tokyo")) + result = tzconversion.tz_convert_from_utc(arr, timezones.maybe_get_tz("Asia/Tokyo")) tm.assert_numpy_array_equal(result, arr) diff --git a/pandas/tseries/frequencies.py b/pandas/tseries/frequencies.py index f94c8ef6550a5..23e08c7550646 100644 --- a/pandas/tseries/frequencies.py +++ b/pandas/tseries/frequencies.py @@ -21,7 +21,6 @@ ) from pandas._libs.tslibs.parsing import get_rule_month from pandas._libs.tslibs.resolution import month_position_check -from pandas._libs.tslibs.timezones import UTC from pandas.util._decorators import cache_readonly from pandas.core.dtypes.common import ( @@ -198,7 +197,9 @@ def __init__(self, index, warn: bool = True): # the timezone so they are in local time if hasattr(index, "tz"): if index.tz is not None: - self.i8values = tzconversion.tz_convert(self.i8values, UTC, index.tz) + self.i8values = tzconversion.tz_convert_from_utc( + self.i8values, index.tz + ) self.warn = warn