Skip to content

Commit 2156c8f

Browse files
jbrockmendelyehoshuadimarsky
authored andcommitted
REF: re-use tz_convert_from_utc_single in _localize_tso (pandas-dev#46525)
1 parent 71ee38c commit 2156c8f

File tree

6 files changed

+57
-44
lines changed

6 files changed

+57
-44
lines changed

pandas/_libs/tslibs/__init__.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@
5555
)
5656
from pandas._libs.tslibs.timestamps import Timestamp
5757
from pandas._libs.tslibs.timezones import tz_compare
58-
from pandas._libs.tslibs.tzconversion import tz_convert_from_utc_single
58+
from pandas._libs.tslibs.tzconversion import (
59+
py_tz_convert_from_utc_single as tz_convert_from_utc_single,
60+
)
5961
from pandas._libs.tslibs.vectorized import (
6062
dt64arr_to_periodarr,
6163
get_resolution,

pandas/_libs/tslibs/conversion.pyx

+8-30
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ from pandas._libs.tslibs.nattype cimport (
7272
)
7373
from pandas._libs.tslibs.tzconversion cimport (
7474
bisect_right_i8,
75-
infer_datetuil_fold,
75+
infer_dateutil_fold,
7676
localize_tzinfo_api,
77+
tz_convert_from_utc_single,
7778
tz_localize_to_utc_single,
7879
)
7980

@@ -531,7 +532,7 @@ cdef _TSObject _create_tsobject_tz_using_offset(npy_datetimestruct dts,
531532
if typ == 'dateutil':
532533
tdata = <int64_t*>cnp.PyArray_DATA(trans)
533534
pos = bisect_right_i8(tdata, obj.value, trans.shape[0]) - 1
534-
obj.fold = infer_datetuil_fold(obj.value, trans, deltas, pos)
535+
obj.fold = infer_dateutil_fold(obj.value, trans, deltas, pos)
535536

536537
# Keep the converter same as PyDateTime's
537538
dt = datetime(obj.dts.year, obj.dts.month, obj.dts.day,
@@ -683,7 +684,7 @@ cdef inline void _localize_tso(_TSObject obj, tzinfo tz):
683684
int64_t[::1] deltas
684685
int64_t local_val
685686
int64_t* tdata
686-
Py_ssize_t pos, ntrans
687+
Py_ssize_t pos, ntrans, outpos = -1
687688
str typ
688689

689690
assert obj.tzinfo is None
@@ -692,35 +693,12 @@ cdef inline void _localize_tso(_TSObject obj, tzinfo tz):
692693
pass
693694
elif obj.value == NPY_NAT:
694695
pass
695-
elif is_tzlocal(tz):
696-
local_val = obj.value + localize_tzinfo_api(obj.value, tz, &obj.fold)
697-
dt64_to_dtstruct(local_val, &obj.dts)
698696
else:
699-
# Adjust datetime64 timestamp, recompute datetimestruct
700-
trans, deltas, typ = get_dst_info(tz)
701-
ntrans = trans.shape[0]
702-
703-
if typ == "pytz":
704-
# i.e. treat_tz_as_pytz(tz)
705-
tdata = <int64_t*>cnp.PyArray_DATA(trans)
706-
pos = bisect_right_i8(tdata, obj.value, ntrans) - 1
707-
local_val = obj.value + deltas[pos]
708-
709-
# find right representation of dst etc in pytz timezone
710-
tz = tz._tzinfos[tz._transition_info[pos]]
711-
elif typ == "dateutil":
712-
# i.e. treat_tz_as_dateutil(tz)
713-
tdata = <int64_t*>cnp.PyArray_DATA(trans)
714-
pos = bisect_right_i8(tdata, obj.value, ntrans) - 1
715-
local_val = obj.value + deltas[pos]
697+
local_val = tz_convert_from_utc_single(obj.value, tz, &obj.fold, &outpos)
716698

717-
# dateutil supports fold, so we infer fold from value
718-
obj.fold = infer_datetuil_fold(obj.value, trans, deltas, pos)
719-
else:
720-
# All other cases have len(deltas) == 1. As of 2018-07-17
721-
# (and 2022-03-07), all test cases that get here have
722-
# is_fixed_offset(tz).
723-
local_val = obj.value + deltas[0]
699+
if outpos != -1:
700+
# infer we went through a pytz path
701+
tz = tz._tzinfos[tz._transition_info[outpos]]
724702

725703
dt64_to_dtstruct(local_val, &obj.dts)
726704

pandas/_libs/tslibs/tzconversion.pxd

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ from numpy cimport (
88
cdef int64_t localize_tzinfo_api(
99
int64_t utc_val, tzinfo tz, bint* fold=*
1010
) except? -1
11-
cpdef int64_t tz_convert_from_utc_single(int64_t val, tzinfo tz)
11+
cdef int64_t tz_convert_from_utc_single(
12+
int64_t utc_val, tzinfo tz, bint* fold=?, Py_ssize_t* outpos=?
13+
) except? -1
1214
cdef int64_t tz_localize_to_utc_single(
1315
int64_t val, tzinfo tz, object ambiguous=*, object nonexistent=*
1416
) except? -1
1517

1618
cdef Py_ssize_t bisect_right_i8(int64_t *data, int64_t val, Py_ssize_t n)
1719

18-
cdef bint infer_datetuil_fold(
20+
cdef bint infer_dateutil_fold(
1921
int64_t value,
2022
const int64_t[::1] trans,
2123
const int64_t[::1] deltas,

pandas/_libs/tslibs/tzconversion.pyi

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ def tz_convert_from_utc(
1212
vals: npt.NDArray[np.int64], # const int64_t[:]
1313
tz: tzinfo,
1414
) -> npt.NDArray[np.int64]: ...
15-
def tz_convert_from_utc_single(val: np.int64, tz: tzinfo) -> np.int64: ...
15+
16+
# py_tz_convert_from_utc_single exposed for testing
17+
def py_tz_convert_from_utc_single(val: np.int64, tz: tzinfo) -> np.int64: ...
1618
def tz_localize_to_utc(
1719
vals: npt.NDArray[np.int64],
1820
tz: tzinfo | None,

pandas/_libs/tslibs/tzconversion.pyx

+38-9
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,18 @@ cdef int64_t localize_tzinfo_api(
444444
return _tz_localize_using_tzinfo_api(utc_val, tz, to_utc=False, fold=fold)
445445

446446

447-
cpdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz):
447+
def py_tz_convert_from_utc_single(int64_t utc_val, tzinfo tz):
448+
# The 'bint* fold=NULL' in tz_convert_from_utc_single means we cannot
449+
# make it cdef, so this is version exposed for testing from python.
450+
return tz_convert_from_utc_single(utc_val, tz)
451+
452+
453+
cdef int64_t tz_convert_from_utc_single(
454+
int64_t utc_val,
455+
tzinfo tz,
456+
bint* fold=NULL,
457+
Py_ssize_t* outpos=NULL,
458+
) except? -1:
448459
"""
449460
Convert the val (in i8) from UTC to tz
450461
@@ -454,6 +465,8 @@ cpdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz):
454465
----------
455466
utc_val : int64
456467
tz : tzinfo
468+
fold : bint*, default NULL
469+
outpos : Py_ssize_t*, default NULL
457470
458471
Returns
459472
-------
@@ -473,15 +486,31 @@ cpdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz):
473486
return utc_val
474487
elif is_tzlocal(tz):
475488
return utc_val + _tz_localize_using_tzinfo_api(utc_val, tz, to_utc=False)
476-
elif is_fixed_offset(tz):
477-
_, deltas, _ = get_dst_info(tz)
478-
delta = deltas[0]
479-
return utc_val + delta
480489
else:
481-
trans, deltas, _ = get_dst_info(tz)
490+
trans, deltas, typ = get_dst_info(tz)
482491
tdata = <int64_t*>cnp.PyArray_DATA(trans)
483-
pos = bisect_right_i8(tdata, utc_val, trans.shape[0]) - 1
484-
return utc_val + deltas[pos]
492+
493+
if typ == "dateutil":
494+
pos = bisect_right_i8(tdata, utc_val, trans.shape[0]) - 1
495+
496+
if fold is not NULL:
497+
fold[0] = infer_dateutil_fold(utc_val, trans, deltas, pos)
498+
return utc_val + deltas[pos]
499+
500+
elif typ == "pytz":
501+
pos = bisect_right_i8(tdata, utc_val, trans.shape[0]) - 1
502+
503+
# We need to get 'pos' back to the caller so it can pick the
504+
# correct "standardized" tzinfo objecg.
505+
if outpos is not NULL:
506+
outpos[0] = pos
507+
return utc_val + deltas[pos]
508+
509+
else:
510+
# All other cases have len(deltas) == 1. As of 2018-07-17
511+
# (and 2022-03-07), all test cases that get here have
512+
# is_fixed_offset(tz).
513+
return utc_val + deltas[0]
485514

486515

487516
def tz_convert_from_utc(const int64_t[:] vals, tzinfo tz):
@@ -635,7 +664,7 @@ cdef int64_t _tz_localize_using_tzinfo_api(
635664

636665

637666
# NB: relies on dateutil internals, subject to change.
638-
cdef bint infer_datetuil_fold(
667+
cdef bint infer_dateutil_fold(
639668
int64_t value,
640669
const int64_t[::1] trans,
641670
const int64_t[::1] deltas,

pandas/tests/tslibs/test_conversion.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
def _compare_utc_to_local(tz_didx):
2323
def f(x):
24-
return tzconversion.tz_convert_from_utc_single(x, tz_didx.tz)
24+
return tzconversion.py_tz_convert_from_utc_single(x, tz_didx.tz)
2525

2626
result = tzconversion.tz_convert_from_utc(tz_didx.asi8, tz_didx.tz)
2727
expected = np.vectorize(f)(tz_didx.asi8)

0 commit comments

Comments
 (0)