@@ -31,10 +31,12 @@ from pandas._libs.tslibs.ccalendar cimport (
3131 DAY_NANOS,
3232 HOUR_NANOS,
3333)
34+ from pandas._libs.tslibs.dtypes cimport periods_per_second
3435from pandas._libs.tslibs.nattype cimport NPY_NAT
3536from pandas._libs.tslibs.np_datetime cimport (
36- dt64_to_dtstruct ,
37+ NPY_DATETIMEUNIT ,
3738 npy_datetimestruct,
39+ pandas_datetime_to_datetimestruct,
3840)
3941from pandas._libs.tslibs.timezones cimport (
4042 get_dst_info,
@@ -54,6 +56,7 @@ cdef const int64_t[::1] _deltas_placeholder = np.array([], dtype=np.int64)
5456cdef class Localizer:
5557 # cdef:
5658 # tzinfo tz
59+ # NPY_DATETIMEUNIT _reso
5760 # bint use_utc, use_fixed, use_tzlocal, use_dst, use_pytz
5861 # ndarray trans
5962 # Py_ssize_t ntrans
@@ -63,8 +66,9 @@ cdef class Localizer:
6366
6467 @ cython.initializedcheck (False )
6568 @ cython.boundscheck (False )
66- def __cinit__ (self , tzinfo tz ):
69+ def __cinit__ (self , tzinfo tz , NPY_DATETIMEUNIT reso ):
6770 self .tz = tz
71+ self ._reso = reso
6872 self .use_utc = self .use_tzlocal = self .use_fixed = False
6973 self .use_dst = self .use_pytz = False
7074 self .ntrans = - 1 # placeholder
@@ -80,19 +84,35 @@ cdef class Localizer:
8084
8185 else :
8286 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+
83103 self .trans = trans
84104 self .ntrans = self .trans.shape[0 ]
85105 self .deltas = deltas
86106
87107 if typ != " pytz" and typ != " dateutil" :
88108 # static/fixed; in this case we know that len(delta) == 1
89109 self .use_fixed = True
90- self .delta = self . deltas[0 ]
110+ self .delta = deltas[0 ]
91111 else :
92112 self .use_dst = True
93113 if typ == " pytz" :
94114 self .use_pytz = True
95- self .tdata = < int64_t* > cnp.PyArray_DATA(self . trans)
115+ self .tdata = < int64_t* > cnp.PyArray_DATA(trans)
96116
97117 @ cython.boundscheck (False )
98118 cdef inline int64_t utc_val_to_local_val(
@@ -102,7 +122,7 @@ cdef class Localizer:
102122 return utc_val
103123 elif self .use_tzlocal:
104124 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
106126 )
107127 elif self .use_fixed:
108128 return utc_val + self .delta
@@ -117,7 +137,11 @@ cdef class Localizer:
117137
118138
119139cdef 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,
121145) except ? - 1 :
122146 """ See tz_localize_to_utc.__doc__"""
123147 cdef:
@@ -131,7 +155,7 @@ cdef int64_t tz_localize_to_utc_single(
131155 return val
132156
133157 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 )
135159
136160 elif is_fixed_offset(tz):
137161 _, deltas, _ = get_dst_info(tz)
@@ -144,13 +168,19 @@ cdef int64_t tz_localize_to_utc_single(
144168 tz,
145169 ambiguous = ambiguous,
146170 nonexistent = nonexistent,
171+ reso = reso,
147172 )[0 ]
148173
149174
150175@ cython.boundscheck (False )
151176@ 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+ ):
154184 """
155185 Localize tzinfo-naive i8 to given time zone (using pytz). If
156186 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,
177207 nonexistent : {None, "NaT", "shift_forward", "shift_backward", "raise", \
178208timedelta-like}
179209 How to handle non-existent times when converting wall times to UTC
210+ reso : NPY_DATETIMEUNIT, default NPY_FR_ns
180211
181212 Returns
182213 -------
@@ -196,7 +227,7 @@ timedelta-like}
196227 bint shift_forward = False , shift_backward = False
197228 bint fill_nonexist = False
198229 str stamp
199- Localizer info = Localizer(tz)
230+ Localizer info = Localizer(tz, reso = reso )
200231
201232 # Vectorized version of DstTzInfo.localize
202233 if info.use_utc:
@@ -210,7 +241,7 @@ timedelta-like}
210241 if v == NPY_NAT:
211242 result[i] = NPY_NAT
212243 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 )
214245 return result.base # to return underlying ndarray
215246
216247 elif info.use_fixed:
@@ -512,7 +543,9 @@ cdef ndarray[int64_t] _get_dst_hours(
512543# ----------------------------------------------------------------------
513544# Timezone Conversion
514545
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 :
516549 """
517550 Convert the val (in i8) from UTC to tz
518551
@@ -522,13 +555,14 @@ cpdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz) except? -1:
522555 ----------
523556 utc_val : int64
524557 tz : tzinfo
558+ reso : NPY_DATETIMEUNIT, default NPY_FR_ns
525559
526560 Returns
527561 -------
528562 converted: int64
529563 """
530564 cdef:
531- Localizer info = Localizer(tz)
565+ Localizer info = Localizer(tz, reso = reso )
532566 Py_ssize_t pos
533567
534568 # 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:
538572# OSError may be thrown by tzlocal on windows at or close to 1970-01-01
539573# see https://github.com/pandas-dev/pandas/pull/37591#issuecomment-720628241
540574cdef 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 ,
542580) except ? - 1 :
543581 """
544582 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(
552590 tz : tzinfo
553591 to_utc : bint
554592 True if converting _to_ UTC, False if going the other direction.
593+ reso : NPY_DATETIMEUNIT
555594 fold : bint*, default NULL
556595 pointer to fold: whether datetime ends up in a fold or not
557596 after adjustment.
@@ -571,8 +610,9 @@ cdef int64_t _tz_localize_using_tzinfo_api(
571610 datetime dt
572611 int64_t delta
573612 timedelta td
613+ int64_t pps = periods_per_second(reso)
574614
575- dt64_to_dtstruct (val, & dts)
615+ pandas_datetime_to_datetimestruct (val, reso , & dts)
576616
577617 # datetime_new is cython-optimized constructor
578618 if not to_utc:
@@ -590,7 +630,7 @@ cdef int64_t _tz_localize_using_tzinfo_api(
590630 dts.min, dts.sec, dts.us, None )
591631
592632 td = tz.utcoffset(dt)
593- delta = int (td.total_seconds() * 1 _000_000_000 )
633+ delta = int (td.total_seconds() * pps )
594634 return delta
595635
596636
0 commit comments