@@ -31,10 +31,12 @@ from pandas._libs.tslibs.ccalendar cimport (
31
31
DAY_NANOS,
32
32
HOUR_NANOS,
33
33
)
34
+ from pandas._libs.tslibs.dtypes cimport periods_per_second
34
35
from pandas._libs.tslibs.nattype cimport NPY_NAT
35
36
from pandas._libs.tslibs.np_datetime cimport (
36
- dt64_to_dtstruct ,
37
+ NPY_DATETIMEUNIT ,
37
38
npy_datetimestruct,
39
+ pandas_datetime_to_datetimestruct,
38
40
)
39
41
from pandas._libs.tslibs.timezones cimport (
40
42
get_dst_info,
@@ -54,6 +56,7 @@ cdef const int64_t[::1] _deltas_placeholder = np.array([], dtype=np.int64)
54
56
cdef class Localizer:
55
57
# cdef:
56
58
# tzinfo tz
59
+ # NPY_DATETIMEUNIT _reso
57
60
# bint use_utc, use_fixed, use_tzlocal, use_dst, use_pytz
58
61
# ndarray trans
59
62
# Py_ssize_t ntrans
@@ -63,8 +66,9 @@ cdef class Localizer:
63
66
64
67
@ cython.initializedcheck (False )
65
68
@ cython.boundscheck (False )
66
- def __cinit__ (self , tzinfo tz ):
69
+ def __cinit__ (self , tzinfo tz , NPY_DATETIMEUNIT reso ):
67
70
self .tz = tz
71
+ self ._reso = reso
68
72
self .use_utc = self .use_tzlocal = self .use_fixed = False
69
73
self .use_dst = self .use_pytz = False
70
74
self .ntrans = - 1 # placeholder
@@ -80,19 +84,35 @@ cdef class Localizer:
80
84
81
85
else :
82
86
trans, deltas, typ = get_dst_info(tz)
87
+ if reso != NPY_DATETIMEUNIT.NPY_FR_ns:
88
+ # NB: using floordiv here is implicitly assuming we will
89
+ # never see trans or deltas that are not an integer number
90
+ # of seconds.
91
+ if reso == NPY_DATETIMEUNIT.NPY_FR_us:
92
+ trans = trans // 1 _000
93
+ deltas = deltas // 1 _000
94
+ elif reso == NPY_DATETIMEUNIT.NPY_FR_ms:
95
+ trans = trans // 1 _000_000
96
+ deltas = deltas // 1 _000_000
97
+ elif reso == NPY_DATETIMEUNIT.NPY_FR_s:
98
+ trans = trans // 1 _000_000_000
99
+ deltas = deltas // 1 _000_000_000
100
+ else :
101
+ raise NotImplementedError (reso)
102
+
83
103
self .trans = trans
84
104
self .ntrans = self .trans.shape[0 ]
85
105
self .deltas = deltas
86
106
87
107
if typ != " pytz" and typ != " dateutil" :
88
108
# static/fixed; in this case we know that len(delta) == 1
89
109
self .use_fixed = True
90
- self .delta = self . deltas[0 ]
110
+ self .delta = deltas[0 ]
91
111
else :
92
112
self .use_dst = True
93
113
if typ == " pytz" :
94
114
self .use_pytz = True
95
- self .tdata = < int64_t* > cnp.PyArray_DATA(self . trans)
115
+ self .tdata = < int64_t* > cnp.PyArray_DATA(trans)
96
116
97
117
@ cython.boundscheck (False )
98
118
cdef inline int64_t utc_val_to_local_val(
@@ -102,7 +122,7 @@ cdef class Localizer:
102
122
return utc_val
103
123
elif self .use_tzlocal:
104
124
return utc_val + _tz_localize_using_tzinfo_api(
105
- utc_val, self .tz, to_utc = False , fold = fold
125
+ utc_val, self .tz, to_utc = False , reso = self ._reso, fold = fold
106
126
)
107
127
elif self .use_fixed:
108
128
return utc_val + self .delta
@@ -117,7 +137,11 @@ cdef class Localizer:
117
137
118
138
119
139
cdef int64_t tz_localize_to_utc_single(
120
- int64_t val, tzinfo tz, object ambiguous = None , object nonexistent = None ,
140
+ int64_t val,
141
+ tzinfo tz,
142
+ object ambiguous = None ,
143
+ object nonexistent = None ,
144
+ NPY_DATETIMEUNIT reso = NPY_DATETIMEUNIT.NPY_FR_ns,
121
145
) except ? - 1 :
122
146
""" See tz_localize_to_utc.__doc__"""
123
147
cdef:
@@ -131,7 +155,7 @@ cdef int64_t tz_localize_to_utc_single(
131
155
return val
132
156
133
157
elif is_tzlocal(tz) or is_zoneinfo(tz):
134
- return val - _tz_localize_using_tzinfo_api(val, tz, to_utc = True )
158
+ return val - _tz_localize_using_tzinfo_api(val, tz, to_utc = True , reso = reso )
135
159
136
160
elif is_fixed_offset(tz):
137
161
_, deltas, _ = get_dst_info(tz)
@@ -144,13 +168,19 @@ cdef int64_t tz_localize_to_utc_single(
144
168
tz,
145
169
ambiguous = ambiguous,
146
170
nonexistent = nonexistent,
171
+ reso = reso,
147
172
)[0 ]
148
173
149
174
150
175
@ cython.boundscheck (False )
151
176
@ cython.wraparound (False )
152
- def tz_localize_to_utc (ndarray[int64_t] vals , tzinfo tz , object ambiguous = None ,
153
- object nonexistent = None ):
177
+ def tz_localize_to_utc (
178
+ ndarray[int64_t] vals ,
179
+ tzinfo tz ,
180
+ object ambiguous = None ,
181
+ object nonexistent = None ,
182
+ NPY_DATETIMEUNIT reso = NPY_DATETIMEUNIT.NPY_FR_ns,
183
+ ):
154
184
"""
155
185
Localize tzinfo-naive i8 to given time zone (using pytz). If
156
186
there are ambiguities in the values, raise AmbiguousTimeError.
@@ -177,6 +207,7 @@ def tz_localize_to_utc(ndarray[int64_t] vals, tzinfo tz, object ambiguous=None,
177
207
nonexistent : {None, "NaT", "shift_forward", "shift_backward", "raise", \
178
208
timedelta-like}
179
209
How to handle non-existent times when converting wall times to UTC
210
+ reso : NPY_DATETIMEUNIT, default NPY_FR_ns
180
211
181
212
Returns
182
213
-------
@@ -196,7 +227,7 @@ timedelta-like}
196
227
bint shift_forward = False , shift_backward = False
197
228
bint fill_nonexist = False
198
229
str stamp
199
- Localizer info = Localizer(tz)
230
+ Localizer info = Localizer(tz, reso = reso )
200
231
201
232
# Vectorized version of DstTzInfo.localize
202
233
if info.use_utc:
@@ -210,7 +241,7 @@ timedelta-like}
210
241
if v == NPY_NAT:
211
242
result[i] = NPY_NAT
212
243
else :
213
- result[i] = v - _tz_localize_using_tzinfo_api(v, tz, to_utc = True )
244
+ result[i] = v - _tz_localize_using_tzinfo_api(v, tz, to_utc = True , reso = reso )
214
245
return result.base # to return underlying ndarray
215
246
216
247
elif info.use_fixed:
@@ -512,7 +543,9 @@ cdef ndarray[int64_t] _get_dst_hours(
512
543
# ----------------------------------------------------------------------
513
544
# Timezone Conversion
514
545
515
- cpdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz) except ? - 1 :
546
+ cpdef int64_t tz_convert_from_utc_single(
547
+ int64_t utc_val, tzinfo tz, NPY_DATETIMEUNIT reso = NPY_DATETIMEUNIT.NPY_FR_ns
548
+ ) except ? - 1 :
516
549
"""
517
550
Convert the val (in i8) from UTC to tz
518
551
@@ -522,13 +555,14 @@ cpdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz) except? -1:
522
555
----------
523
556
utc_val : int64
524
557
tz : tzinfo
558
+ reso : NPY_DATETIMEUNIT, default NPY_FR_ns
525
559
526
560
Returns
527
561
-------
528
562
converted: int64
529
563
"""
530
564
cdef:
531
- Localizer info = Localizer(tz)
565
+ Localizer info = Localizer(tz, reso = reso )
532
566
Py_ssize_t pos
533
567
534
568
# Note: caller is responsible for ensuring utc_val != NPY_NAT
@@ -538,7 +572,11 @@ cpdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz) except? -1:
538
572
# OSError may be thrown by tzlocal on windows at or close to 1970-01-01
539
573
# see https://github.com/pandas-dev/pandas/pull/37591#issuecomment-720628241
540
574
cdef int64_t _tz_localize_using_tzinfo_api(
541
- int64_t val, tzinfo tz, bint to_utc = True , bint* fold = NULL
575
+ int64_t val,
576
+ tzinfo tz,
577
+ bint to_utc = True ,
578
+ NPY_DATETIMEUNIT reso = NPY_DATETIMEUNIT.NPY_FR_ns,
579
+ bint* fold = NULL ,
542
580
) except ? - 1 :
543
581
"""
544
582
Convert the i8 representation of a datetime from a general-case timezone to
@@ -552,6 +590,7 @@ cdef int64_t _tz_localize_using_tzinfo_api(
552
590
tz : tzinfo
553
591
to_utc : bint
554
592
True if converting _to_ UTC, False if going the other direction.
593
+ reso : NPY_DATETIMEUNIT
555
594
fold : bint*, default NULL
556
595
pointer to fold: whether datetime ends up in a fold or not
557
596
after adjustment.
@@ -571,8 +610,9 @@ cdef int64_t _tz_localize_using_tzinfo_api(
571
610
datetime dt
572
611
int64_t delta
573
612
timedelta td
613
+ int64_t pps = periods_per_second(reso)
574
614
575
- dt64_to_dtstruct (val, & dts)
615
+ pandas_datetime_to_datetimestruct (val, reso , & dts)
576
616
577
617
# datetime_new is cython-optimized constructor
578
618
if not to_utc:
@@ -590,7 +630,7 @@ cdef int64_t _tz_localize_using_tzinfo_api(
590
630
dts.min, dts.sec, dts.us, None )
591
631
592
632
td = tz.utcoffset(dt)
593
- delta = int (td.total_seconds() * 1 _000_000_000 )
633
+ delta = int (td.total_seconds() * pps )
594
634
return delta
595
635
596
636
0 commit comments