Skip to content

REF: simplify tzconversion #47019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions pandas/_libs/tslibs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 3 additions & 9 deletions pandas/_libs/tslibs/offsets.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 (
Expand Down Expand Up @@ -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]":
Expand Down
27 changes: 12 additions & 15 deletions pandas/_libs/tslibs/timestamps.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
4 changes: 2 additions & 2 deletions pandas/_libs/tslibs/tzconversion.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -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=*
Expand Down
4 changes: 2 additions & 2 deletions pandas/_libs/tslibs/tzconversion.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
54 changes: 14 additions & 40 deletions pandas/_libs/tslibs/tzconversion.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -259,14 +257,9 @@ timedelta-like}
"shift_backwards} or a timedelta object")
raise ValueError(msg)

trans, deltas, _ = get_dst_info(tz)

tdata = <int64_t*>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)
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand All @@ -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
-------
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/tslibs/test_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down