diff --git a/pandas/_libs/tslibs/__init__.py b/pandas/_libs/tslibs/__init__.py index b3a006141fadc..72bc6886b5175 100644 --- a/pandas/_libs/tslibs/__init__.py +++ b/pandas/_libs/tslibs/__init__.py @@ -58,9 +58,7 @@ ) from pandas._libs.tslibs.timestamps import Timestamp from pandas._libs.tslibs.timezones import tz_compare -from pandas._libs.tslibs.tzconversion import ( - py_tz_convert_from_utc_single as tz_convert_from_utc_single, -) +from pandas._libs.tslibs.tzconversion import tz_convert_from_utc_single from pandas._libs.tslibs.vectorized import ( dt64arr_to_periodarr, get_resolution, diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 6c96df9a7ea0b..d06dc3160995d 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -54,10 +54,7 @@ from pandas._libs.tslibs.ccalendar cimport ( get_firstbday, get_lastbday, ) -from pandas._libs.tslibs.conversion cimport ( - convert_datetime_to_tsobject, - localize_pydatetime, -) +from pandas._libs.tslibs.conversion cimport localize_pydatetime from pandas._libs.tslibs.nattype cimport ( NPY_NAT, c_NaT as NaT, @@ -68,7 +65,6 @@ from pandas._libs.tslibs.np_datetime cimport ( npy_datetimestruct, pydate_to_dtstruct, ) -from pandas._libs.tslibs.tzconversion cimport tz_convert_from_utc_single from .dtypes cimport PeriodDtypeCode from .timedeltas cimport ( @@ -270,10 +266,8 @@ cdef _to_dt64D(dt): if getattr(dt, 'tzinfo', None) is not None: # Get the nanosecond timestamp, # 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_from_utc_single(i8, dt.tzinfo) - dt = np.int64(dt).astype('datetime64[ns]') + naive = dt.astimezone(None) + dt = np.datetime64(naive, "D") else: dt = np.datetime64(dt) if dt.dtype.name != "datetime64[D]": diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index fcc9390a2cccd..abdb4aebb625f 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -1979,22 +1979,19 @@ default 'raise' value = tz_localize_to_utc_single(self.value, tz, ambiguous=ambiguous, nonexistent=nonexistent) - out = Timestamp(value, tz=tz) - if out is not NaT: - out._set_freq(self._freq) # avoid warning in constructor - return out + elif tz is None: + # reset tz + value = tz_convert_from_utc_single(self.value, self.tz) + else: - if tz is None: - # reset tz - value = tz_convert_from_utc_single(self.value, self.tz) - out = Timestamp(value, tz=tz) - if out is not NaT: - out._set_freq(self._freq) # avoid warning in constructor - return out - else: - raise TypeError( - "Cannot localize tz-aware Timestamp, use tz_convert for conversions" - ) + raise TypeError( + "Cannot localize tz-aware Timestamp, use tz_convert for conversions" + ) + + out = Timestamp(value, tz=tz) + if out is not NaT: + out._set_freq(self._freq) # avoid warning in constructor + return out def tz_convert(self, tz): """ diff --git a/pandas/_libs/tslibs/tzconversion.pxd b/pandas/_libs/tslibs/tzconversion.pxd index 600ac54639dfc..2acad9ea34062 100644 --- a/pandas/_libs/tslibs/tzconversion.pxd +++ b/pandas/_libs/tslibs/tzconversion.pxd @@ -6,8 +6,8 @@ from numpy cimport ( ) -cdef int64_t tz_convert_from_utc_single( - int64_t utc_val, tzinfo tz, bint* fold=?, Py_ssize_t* outpos=? +cpdef int64_t tz_convert_from_utc_single( + int64_t utc_val, tzinfo tz ) except? -1 cdef int64_t tz_localize_to_utc_single( int64_t val, tzinfo tz, object ambiguous=*, object nonexistent=* diff --git a/pandas/_libs/tslibs/tzconversion.pyi b/pandas/_libs/tslibs/tzconversion.pyi index 8647dee712294..2531383b658fc 100644 --- a/pandas/_libs/tslibs/tzconversion.pyi +++ b/pandas/_libs/tslibs/tzconversion.pyi @@ -8,8 +8,8 @@ import numpy as np from pandas._typing import npt -# py_tz_convert_from_utc_single exposed for testing -def py_tz_convert_from_utc_single(val: np.int64, tz: tzinfo) -> np.int64: ... +# tz_convert_from_utc_single exposed for testing +def tz_convert_from_utc_single(val: np.int64, tz: tzinfo) -> np.int64: ... def tz_localize_to_utc( vals: npt.NDArray[np.int64], tz: tzinfo | None, diff --git a/pandas/_libs/tslibs/tzconversion.pyx b/pandas/_libs/tslibs/tzconversion.pyx index fede9768f5fee..0cdc7b777f45f 100644 --- a/pandas/_libs/tslibs/tzconversion.pyx +++ b/pandas/_libs/tslibs/tzconversion.pyx @@ -183,29 +183,28 @@ timedelta-like} localized : ndarray[int64_t] """ cdef: - const int64_t[::1] deltas ndarray[uint8_t, cast=True] ambiguous_array - Py_ssize_t i, idx, pos, ntrans, n = vals.shape[0] + Py_ssize_t i, idx, pos, n = vals.shape[0] Py_ssize_t delta_idx_offset, delta_idx, pos_left, pos_right - int64_t *tdata int64_t v, left, right, val, new_local, remaining_mins int64_t first_delta, delta int64_t shift_delta = 0 - ndarray[int64_t] trans, result_a, result_b, dst_hours + ndarray[int64_t] result_a, result_b, dst_hours int64_t[::1] result npy_datetimestruct dts bint infer_dst = False, is_dst = False, fill = False bint shift_forward = False, shift_backward = False bint fill_nonexist = False str stamp + Localizer info = Localizer(tz) # Vectorized version of DstTzInfo.localize - if is_utc(tz) or tz is None: + if info.use_utc: return vals.copy() result = cnp.PyArray_EMPTY(vals.ndim, vals.shape, cnp.NPY_INT64, 0) - if is_tzlocal(tz) or is_zoneinfo(tz): + if info.use_tzlocal: for i in range(n): v = vals[i] if v == NPY_NAT: @@ -214,9 +213,8 @@ timedelta-like} result[i] = v - _tz_localize_using_tzinfo_api(v, tz, to_utc=True) return result.base # to return underlying ndarray - elif is_fixed_offset(tz): - _, deltas, _ = get_dst_info(tz) - delta = deltas[0] + elif info.use_fixed: + delta = info.delta for i in range(n): v = vals[i] if v == NPY_NAT: @@ -259,14 +257,9 @@ timedelta-like} "shift_backwards} or a timedelta object") raise ValueError(msg) - trans, deltas, _ = get_dst_info(tz) - - tdata = cnp.PyArray_DATA(trans) - ntrans = trans.shape[0] - # Determine whether each date lies left of the DST transition (store in # result_a) or right of the DST transition (store in result_b) - result_a, result_b =_get_utc_bounds(vals, tdata, ntrans, deltas) + result_a, result_b =_get_utc_bounds(vals, info.tdata, info.ntrans, info.deltas) # silence false-positive compiler warning dst_hours = np.empty(0, dtype=np.int64) @@ -278,7 +271,7 @@ timedelta-like} # Shift the delta_idx by if the UTC offset of # the target tz is greater than 0 and we're moving forward # or vice versa - first_delta = deltas[0] + first_delta = info.deltas[0] if (shift_forward or shift_delta > 0) and first_delta > 0: delta_idx_offset = 1 elif (shift_backward or shift_delta < 0) and first_delta < 0: @@ -336,10 +329,10 @@ timedelta-like} # nonexistent times new_local = val - remaining_mins - 1 - delta_idx = bisect_right_i8(tdata, new_local, ntrans) + delta_idx = bisect_right_i8(info.tdata, new_local, info.ntrans) delta_idx = delta_idx - delta_idx_offset - result[i] = new_local - deltas[delta_idx] + result[i] = new_local - info.deltas[delta_idx] elif fill_nonexist: result[i] = NPY_NAT else: @@ -519,19 +512,7 @@ cdef ndarray[int64_t] _get_dst_hours( # ---------------------------------------------------------------------- # Timezone Conversion -def py_tz_convert_from_utc_single(int64_t utc_val, tzinfo tz): - # The 'bint* fold=NULL' in tz_convert_from_utc_single means we cannot - # make it cdef, so this is version exposed for testing from python. - return tz_convert_from_utc_single(utc_val, tz) - - -@cython.boundscheck(False) -cdef int64_t tz_convert_from_utc_single( - int64_t utc_val, - tzinfo tz, - bint* fold=NULL, - Py_ssize_t* outpos=NULL, -) except? -1: +cpdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz) except? -1: """ Convert the val (in i8) from UTC to tz @@ -541,8 +522,6 @@ cdef int64_t tz_convert_from_utc_single( ---------- utc_val : int64 tz : tzinfo - fold : bint*, default NULL - outpos : Py_ssize_t*, default NULL Returns ------- @@ -552,13 +531,8 @@ cdef int64_t tz_convert_from_utc_single( Localizer info = Localizer(tz) Py_ssize_t pos - if utc_val == NPY_NAT: - return utc_val - - if outpos is not NULL and info.use_pytz: - return info.utc_val_to_local_val(utc_val, outpos, fold) - else: - return info.utc_val_to_local_val(utc_val, &pos, fold) + # Note: caller is responsible for ensuring utc_val != NPY_NAT + return info.utc_val_to_local_val(utc_val, &pos) # OSError may be thrown by tzlocal on windows at or close to 1970-01-01 diff --git a/pandas/tests/tslibs/test_conversion.py b/pandas/tests/tslibs/test_conversion.py index 99be0e63d58e2..ade5a2077767f 100644 --- a/pandas/tests/tslibs/test_conversion.py +++ b/pandas/tests/tslibs/test_conversion.py @@ -22,7 +22,7 @@ def _compare_utc_to_local(tz_didx): def f(x): - return tzconversion.py_tz_convert_from_utc_single(x, tz_didx.tz) + return tzconversion.tz_convert_from_utc_single(x, tz_didx.tz) result = tz_convert_from_utc(tz_didx.asi8, tz_didx.tz) expected = np.vectorize(f)(tz_didx.asi8)