Skip to content

Commit 41942e1

Browse files
authored
REF: Use Localizer more (#46898)
* REF: move Localizer to tzconversion * re-use utc_val_to_local_val * reuse Localizer * CLN: remove no-longer-used
1 parent 61f61c3 commit 41942e1

File tree

4 files changed

+115
-142
lines changed

4 files changed

+115
-142
lines changed

pandas/_libs/tslibs/conversion.pyx

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,8 @@ from pandas._libs.tslibs.np_datetime import (
5252
)
5353

5454
from pandas._libs.tslibs.timezones cimport (
55-
get_dst_info,
5655
get_utcoffset,
57-
is_fixed_offset,
58-
is_tzlocal,
5956
is_utc,
60-
is_zoneinfo,
6157
maybe_get_tz,
6258
tz_compare,
6359
utc_pytz as UTC,
@@ -77,10 +73,7 @@ from pandas._libs.tslibs.nattype cimport (
7773
checknull_with_nat,
7874
)
7975
from pandas._libs.tslibs.tzconversion cimport (
80-
bisect_right_i8,
81-
infer_dateutil_fold,
82-
localize_tzinfo_api,
83-
tz_convert_from_utc_single,
76+
Localizer,
8477
tz_localize_to_utc_single,
8578
)
8679

@@ -518,9 +511,7 @@ cdef _TSObject _create_tsobject_tz_using_offset(npy_datetimestruct dts,
518511
_TSObject obj = _TSObject()
519512
int64_t value # numpy dt64
520513
datetime dt
521-
ndarray[int64_t] trans
522-
int64_t* tdata
523-
int64_t[::1] deltas
514+
Py_ssize_t pos
524515

525516
value = dtstruct_to_dt64(&dts)
526517
obj.dts = dts
@@ -530,19 +521,18 @@ cdef _TSObject _create_tsobject_tz_using_offset(npy_datetimestruct dts,
530521
check_overflows(obj)
531522
return obj
532523

524+
cdef:
525+
Localizer info = Localizer(tz)
526+
533527
# Infer fold from offset-adjusted obj.value
534528
# see PEP 495 https://www.python.org/dev/peps/pep-0495/#the-fold-attribute
535-
if is_utc(tz):
529+
if info.use_utc:
536530
pass
537-
elif is_tzlocal(tz) or is_zoneinfo(tz):
538-
localize_tzinfo_api(obj.value, tz, &obj.fold)
539-
else:
540-
trans, deltas, typ = get_dst_info(tz)
541-
542-
if typ == 'dateutil':
543-
tdata = <int64_t*>cnp.PyArray_DATA(trans)
544-
pos = bisect_right_i8(tdata, obj.value, trans.shape[0]) - 1
545-
obj.fold = infer_dateutil_fold(obj.value, trans, deltas, pos)
531+
elif info.use_tzlocal:
532+
info.utc_val_to_local_val(obj.value, &pos, &obj.fold)
533+
elif info.use_dst and not info.use_pytz:
534+
# i.e. dateutil
535+
info.utc_val_to_local_val(obj.value, &pos, &obj.fold)
546536

547537
# Keep the converter same as PyDateTime's
548538
dt = datetime(obj.dts.year, obj.dts.month, obj.dts.day,
@@ -700,18 +690,19 @@ cdef inline void _localize_tso(_TSObject obj, tzinfo tz):
700690
cdef:
701691
int64_t local_val
702692
Py_ssize_t outpos = -1
693+
Localizer info = Localizer(tz)
703694

704695
assert obj.tzinfo is None
705696

706-
if is_utc(tz):
697+
if info.use_utc:
707698
pass
708699
elif obj.value == NPY_NAT:
709700
pass
710701
else:
711-
local_val = tz_convert_from_utc_single(obj.value, tz, &obj.fold, &outpos)
702+
local_val = info.utc_val_to_local_val(obj.value, &outpos, &obj.fold)
712703

713-
if outpos != -1:
714-
# infer we went through a pytz path
704+
if info.use_pytz:
705+
# infer we went through a pytz path, will have outpos!=-1
715706
tz = tz._tzinfos[tz._transition_info[outpos]]
716707

717708
dt64_to_dtstruct(local_val, &obj.dts)

pandas/_libs/tslibs/tzconversion.pxd

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ from cpython.datetime cimport tzinfo
22
from numpy cimport (
33
int64_t,
44
intp_t,
5+
ndarray,
56
)
67

78

@@ -21,5 +22,23 @@ cdef bint infer_dateutil_fold(
2122
int64_t value,
2223
const int64_t[::1] trans,
2324
const int64_t[::1] deltas,
24-
intp_t pos,
25+
Py_ssize_t pos,
2526
)
27+
28+
29+
cdef class Localizer:
30+
cdef:
31+
tzinfo tz
32+
bint use_utc, use_fixed, use_tzlocal, use_dst, use_pytz
33+
ndarray trans
34+
Py_ssize_t ntrans
35+
const int64_t[::1] deltas
36+
int64_t delta
37+
int64_t* tdata
38+
39+
cdef inline int64_t utc_val_to_local_val(
40+
self,
41+
int64_t utc_val,
42+
Py_ssize_t* pos,
43+
bint* fold=?,
44+
) except? -1

pandas/_libs/tslibs/tzconversion.pyx

Lines changed: 76 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,75 @@ from pandas._libs.tslibs.timezones cimport (
4646
)
4747

4848

49+
cdef const int64_t[::1] _deltas_placeholder = np.array([], dtype=np.int64)
50+
51+
52+
@cython.freelist(16)
53+
#@cython.internal
54+
@cython.final
55+
cdef class Localizer:
56+
# cdef:
57+
# tzinfo tz
58+
# bint use_utc, use_fixed, use_tzlocal, use_dst, use_pytz
59+
# ndarray trans
60+
# Py_ssize_t ntrans
61+
# const int64_t[::1] deltas
62+
# int64_t delta
63+
# int64_t* tdata
64+
65+
@cython.initializedcheck(False)
66+
@cython.boundscheck(False)
67+
def __cinit__(self, tzinfo tz):
68+
self.tz = tz
69+
self.use_utc = self.use_tzlocal = self.use_fixed = False
70+
self.use_dst = self.use_pytz = False
71+
self.ntrans = -1 # placeholder
72+
self.delta = -1 # placeholder
73+
self.deltas = _deltas_placeholder
74+
self.tdata = NULL
75+
76+
if is_utc(tz) or tz is None:
77+
self.use_utc = True
78+
79+
elif is_tzlocal(tz) or is_zoneinfo(tz):
80+
self.use_tzlocal = True
81+
82+
else:
83+
trans, deltas, typ = get_dst_info(tz)
84+
self.trans = trans
85+
self.ntrans = self.trans.shape[0]
86+
self.deltas = deltas
87+
88+
if typ != "pytz" and typ != "dateutil":
89+
# static/fixed; in this case we know that len(delta) == 1
90+
self.use_fixed = True
91+
self.delta = self.deltas[0]
92+
else:
93+
self.use_dst = True
94+
if typ == "pytz":
95+
self.use_pytz = True
96+
self.tdata = <int64_t*>cnp.PyArray_DATA(self.trans)
97+
98+
@cython.boundscheck(False)
99+
cdef inline int64_t utc_val_to_local_val(
100+
self, int64_t utc_val, Py_ssize_t* pos, bint* fold=NULL
101+
) except? -1:
102+
if self.use_utc:
103+
return utc_val
104+
elif self.use_tzlocal:
105+
return utc_val + localize_tzinfo_api(utc_val, self.tz, fold)
106+
elif self.use_fixed:
107+
return utc_val + self.delta
108+
else:
109+
pos[0] = bisect_right_i8(self.tdata, utc_val, self.ntrans) - 1
110+
if fold is not NULL:
111+
fold[0] = infer_dateutil_fold(
112+
utc_val, self.trans, self.deltas, pos[0]
113+
)
114+
115+
return utc_val + self.deltas[pos[0]]
116+
117+
49118
cdef int64_t tz_localize_to_utc_single(
50119
int64_t val, tzinfo tz, object ambiguous=None, object nonexistent=None,
51120
) except? -1:
@@ -465,44 +534,16 @@ cdef int64_t tz_convert_from_utc_single(
465534
converted: int64
466535
"""
467536
cdef:
468-
int64_t delta
469-
int64_t[::1] deltas
470-
ndarray[int64_t, ndim=1] trans
471-
int64_t* tdata
472-
intp_t pos
537+
Localizer info = Localizer(tz)
538+
Py_ssize_t pos
473539

474540
if utc_val == NPY_NAT:
475541
return utc_val
476542

477-
if is_utc(tz):
478-
return utc_val
479-
elif is_tzlocal(tz) or is_zoneinfo(tz):
480-
return utc_val + _tz_localize_using_tzinfo_api(utc_val, tz, to_utc=False, fold=fold)
543+
if outpos is not NULL and info.use_pytz:
544+
return info.utc_val_to_local_val(utc_val, outpos, fold)
481545
else:
482-
trans, deltas, typ = get_dst_info(tz)
483-
tdata = <int64_t*>cnp.PyArray_DATA(trans)
484-
485-
if typ == "dateutil":
486-
pos = bisect_right_i8(tdata, utc_val, trans.shape[0]) - 1
487-
488-
if fold is not NULL:
489-
fold[0] = infer_dateutil_fold(utc_val, trans, deltas, pos)
490-
return utc_val + deltas[pos]
491-
492-
elif typ == "pytz":
493-
pos = bisect_right_i8(tdata, utc_val, trans.shape[0]) - 1
494-
495-
# We need to get 'pos' back to the caller so it can pick the
496-
# correct "standardized" tzinfo object.
497-
if outpos is not NULL:
498-
outpos[0] = pos
499-
return utc_val + deltas[pos]
500-
501-
else:
502-
# All other cases have len(deltas) == 1. As of 2018-07-17
503-
# (and 2022-03-07), all test cases that get here have
504-
# is_fixed_offset(tz).
505-
return utc_val + deltas[0]
546+
return info.utc_val_to_local_val(utc_val, &pos, fold)
506547

507548

508549
# OSError may be thrown by tzlocal on windows at or close to 1970-01-01
@@ -571,7 +612,7 @@ cdef bint infer_dateutil_fold(
571612
int64_t value,
572613
const int64_t[::1] trans,
573614
const int64_t[::1] deltas,
574-
intp_t pos,
615+
Py_ssize_t pos,
575616
):
576617
"""
577618
Infer _TSObject fold property from value by assuming 0 and then setting
@@ -584,7 +625,7 @@ cdef bint infer_dateutil_fold(
584625
ndarray of offset transition points in nanoseconds since epoch.
585626
deltas : int64_t[:]
586627
array of offsets corresponding to transition points in trans.
587-
pos : intp_t
628+
pos : Py_ssize_t
588629
Position of the last transition point before taking fold into account.
589630
590631
Returns

pandas/_libs/tslibs/vectorized.pyx

Lines changed: 3 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -32,78 +32,8 @@ from .np_datetime cimport (
3232
from .offsets cimport BaseOffset
3333
from .period cimport get_period_ordinal
3434
from .timestamps cimport create_timestamp_from_ts
35-
from .timezones cimport (
36-
get_dst_info,
37-
is_tzlocal,
38-
is_utc,
39-
is_zoneinfo,
40-
)
41-
from .tzconversion cimport (
42-
bisect_right_i8,
43-
localize_tzinfo_api,
44-
)
45-
46-
47-
cdef const int64_t[::1] _deltas_placeholder = np.array([], dtype=np.int64)
48-
49-
50-
@cython.freelist(16)
51-
@cython.internal
52-
@cython.final
53-
cdef class Localizer:
54-
cdef:
55-
tzinfo tz
56-
bint use_utc, use_fixed, use_tzlocal, use_dst, use_pytz
57-
ndarray trans
58-
Py_ssize_t ntrans
59-
const int64_t[::1] deltas
60-
int64_t delta
61-
int64_t* tdata
62-
63-
@cython.initializedcheck(False)
64-
@cython.boundscheck(False)
65-
def __cinit__(self, tzinfo tz):
66-
self.tz = tz
67-
self.use_utc = self.use_tzlocal = self.use_fixed = False
68-
self.use_dst = self.use_pytz = False
69-
self.ntrans = -1 # placeholder
70-
self.delta = -1 # placeholder
71-
self.deltas = _deltas_placeholder
72-
self.tdata = NULL
73-
74-
if is_utc(tz) or tz is None:
75-
self.use_utc = True
76-
77-
elif is_tzlocal(tz) or is_zoneinfo(tz):
78-
self.use_tzlocal = True
79-
80-
else:
81-
trans, deltas, typ = get_dst_info(tz)
82-
self.trans = trans
83-
self.ntrans = self.trans.shape[0]
84-
self.deltas = deltas
85-
86-
if typ != "pytz" and typ != "dateutil":
87-
# static/fixed; in this case we know that len(delta) == 1
88-
self.use_fixed = True
89-
self.delta = self.deltas[0]
90-
else:
91-
self.use_dst = True
92-
if typ == "pytz":
93-
self.use_pytz = True
94-
self.tdata = <int64_t*>cnp.PyArray_DATA(self.trans)
95-
96-
@cython.boundscheck(False)
97-
cdef inline int64_t utc_val_to_local_val(self, int64_t utc_val, Py_ssize_t* pos) except? -1:
98-
if self.use_utc:
99-
return utc_val
100-
elif self.use_tzlocal:
101-
return utc_val + localize_tzinfo_api(utc_val, self.tz)
102-
elif self.use_fixed:
103-
return utc_val + self.delta
104-
else:
105-
pos[0] = bisect_right_i8(self.tdata, utc_val, self.ntrans) - 1
106-
return utc_val + self.deltas[pos[0]]
35+
from .timezones cimport is_utc
36+
from .tzconversion cimport Localizer
10737

10838

10939
@cython.boundscheck(False)
@@ -140,15 +70,7 @@ def tz_convert_from_utc(const int64_t[:] stamps, tzinfo tz):
14070
result[i] = NPY_NAT
14171
continue
14272

143-
if info.use_utc:
144-
local_val = utc_val
145-
elif info.use_tzlocal:
146-
local_val = utc_val + localize_tzinfo_api(utc_val, tz)
147-
elif info.use_fixed:
148-
local_val = utc_val + info.delta
149-
else:
150-
pos = bisect_right_i8(info.tdata, utc_val, info.ntrans) - 1
151-
local_val = utc_val + info.deltas[pos]
73+
local_val = info.utc_val_to_local_val(utc_val, &pos)
15274

15375
result[i] = local_val
15476

0 commit comments

Comments
 (0)