diff --git a/pandas/_libs/tslibs/resolution.pyx b/pandas/_libs/tslibs/resolution.pyx index d5f10374d2860..d6c78cfe27ea0 100644 --- a/pandas/_libs/tslibs/resolution.pyx +++ b/pandas/_libs/tslibs/resolution.pyx @@ -1,17 +1,15 @@ from cpython.datetime cimport tzinfo import numpy as np -from numpy cimport ndarray, int64_t, int32_t +from numpy cimport ndarray, int64_t, int32_t, intp_t from pandas._libs.tslibs.util cimport get_nat from pandas._libs.tslibs.dtypes import Resolution from pandas._libs.tslibs.np_datetime cimport ( npy_datetimestruct, dt64_to_dtstruct) -from pandas._libs.tslibs.timezones cimport ( - is_utc, is_tzlocal, get_dst_info) from pandas._libs.tslibs.ccalendar cimport get_days_in_month -from pandas._libs.tslibs.tzconversion cimport tz_convert_utc_to_tzlocal +from pandas._libs.tslibs.tzconversion cimport Localizer # ---------------------------------------------------------------------- # Constants @@ -39,51 +37,19 @@ def get_resolution(const int64_t[:] stamps, tzinfo tz=None): Py_ssize_t i, n = len(stamps) npy_datetimestruct dts int reso = RESO_DAY, curr_reso - ndarray[int64_t] trans - int64_t[:] deltas - Py_ssize_t[:] pos - int64_t local_val, delta - - if is_utc(tz) or tz is None: - for i in range(n): - if stamps[i] == NPY_NAT: - continue - dt64_to_dtstruct(stamps[i], &dts) - curr_reso = _reso_stamp(&dts) - if curr_reso < reso: - reso = curr_reso - elif is_tzlocal(tz): - for i in range(n): - if stamps[i] == NPY_NAT: - continue - local_val = tz_convert_utc_to_tzlocal(stamps[i], tz) - dt64_to_dtstruct(local_val, &dts) - curr_reso = _reso_stamp(&dts) - if curr_reso < reso: - reso = curr_reso - else: - # Adjust datetime64 timestamp, recompute datetimestruct - trans, deltas, typ = get_dst_info(tz) - - if typ not in ['pytz', 'dateutil']: - # static/fixed; in this case we know that len(delta) == 1 - delta = deltas[0] - for i in range(n): - if stamps[i] == NPY_NAT: - continue - dt64_to_dtstruct(stamps[i] + delta, &dts) - curr_reso = _reso_stamp(&dts) - if curr_reso < reso: - reso = curr_reso - else: - pos = trans.searchsorted(stamps, side='right') - 1 - for i in range(n): - if stamps[i] == NPY_NAT: - continue - dt64_to_dtstruct(stamps[i] + deltas[pos[i]], &dts) - curr_reso = _reso_stamp(&dts) - if curr_reso < reso: - reso = curr_reso + int64_t local_val + Localizer localizer = Localizer(tz, stamps) + + for i in range(n): + if stamps[i] == NPY_NAT: + continue + + local_val = localizer.get_local_timestamp(stamps[i], i) + + dt64_to_dtstruct(local_val, &dts) + curr_reso = _reso_stamp(&dts) + if curr_reso < reso: + reso = curr_reso return Resolution(reso) diff --git a/pandas/_libs/tslibs/timezones.pxd b/pandas/_libs/tslibs/timezones.pxd index f51ee41cb99a6..aff62fa9bba52 100644 --- a/pandas/_libs/tslibs/timezones.pxd +++ b/pandas/_libs/tslibs/timezones.pxd @@ -1,5 +1,7 @@ from cpython.datetime cimport datetime, timedelta, tzinfo +from numpy cimport int64_t, intp_t, ndarray + cdef tzinfo utc_pytz cpdef bint is_utc(tzinfo tz) diff --git a/pandas/_libs/tslibs/timezones.pyx b/pandas/_libs/tslibs/timezones.pyx index 3b2104f75956a..8a6b7e0743e90 100644 --- a/pandas/_libs/tslibs/timezones.pyx +++ b/pandas/_libs/tslibs/timezones.pyx @@ -17,7 +17,7 @@ UTC = pytz.utc import numpy as np cimport numpy as cnp -from numpy cimport int64_t +from numpy cimport int64_t, intp_t, ndarray cnp.import_array() # ---------------------------------------------------------------------- @@ -192,10 +192,10 @@ cdef object _get_utc_trans_times_from_dateutil_tz(tzinfo tz): return new_trans -cdef int64_t[:] unbox_utcoffsets(object transinfo): +cdef ndarray[int64_t, ndim=1] unbox_utcoffsets(object transinfo): cdef: Py_ssize_t i, sz - int64_t[:] arr + ndarray[int64_t, ndim=1] arr sz = len(transinfo) arr = np.empty(sz, dtype='i8') @@ -209,7 +209,6 @@ cdef int64_t[:] unbox_utcoffsets(object transinfo): # ---------------------------------------------------------------------- # Daylight Savings - cdef object get_dst_info(tzinfo tz): """ Returns diff --git a/pandas/_libs/tslibs/tzconversion.pxd b/pandas/_libs/tslibs/tzconversion.pxd index 7d102868256de..3e1cbc9df0010 100644 --- a/pandas/_libs/tslibs/tzconversion.pxd +++ b/pandas/_libs/tslibs/tzconversion.pxd @@ -1,5 +1,5 @@ from cpython.datetime cimport tzinfo -from numpy cimport int64_t +from numpy cimport int64_t, intp_t, ndarray cdef int64_t tz_convert_utc_to_tzlocal(int64_t utc_val, tzinfo tz, bint* fold=*) @@ -7,3 +7,16 @@ cpdef int64_t tz_convert_single(int64_t val, tzinfo tz1, tzinfo tz2) cdef int64_t tz_localize_to_utc_single( int64_t val, tzinfo tz, object ambiguous=*, object nonexistent=* ) except? -1 + + +cdef class Localizer: + cdef: + bint use_utc, use_tzlocal, use_fixed, use_pytz + int noffsets + int64_t* utcoffsets + intp_t* positions + ndarray positions_arr # needed to avoid segfault + int64_t delta + tzinfo tz + + cdef inline int64_t get_local_timestamp(self, int64_t utc_value, Py_ssize_t i) diff --git a/pandas/_libs/tslibs/tzconversion.pyx b/pandas/_libs/tslibs/tzconversion.pyx index 98c40e109dbab..45dc2b1de39c3 100644 --- a/pandas/_libs/tslibs/tzconversion.pyx +++ b/pandas/_libs/tslibs/tzconversion.pyx @@ -29,6 +29,61 @@ from pandas._libs.tslibs.timezones cimport ( ) +cdef class Localizer: + # cdef: + # bint use_utc, use_tzlocal, use_fixed, use_pytz + # int noffsets + # int64_t* utcoffsets + # intp_t* positions + # ndarray positions_arr # needed to avoid segfault + # int64_t delta + # tzinfo tz + + def __cinit__(self, tzinfo tz, int64_t[:] values): + cdef: + ndarray[intp_t, ndim=1] pos + ndarray[int64_t, ndim=1] deltas + + self.use_utc = self.use_tzlocal = self.use_fixed = self.use_pytz = False + self.delta = NPY_NAT # placeholder + self.utcoffsets = NULL + self.positions = NULL + self.noffsets = 0 + self.tz = tz + + if tz is None or is_utc(tz): + self.use_utc = True + elif is_tzlocal(tz): + self.use_tzlocal = True + else: + trans, deltas, typ = get_dst_info(tz) + self.noffsets = len(deltas) + if typ not in ["pytz", "dateutil"]: + # Fixed Offset + self.use_fixed = True + self.delta = deltas[0] + else: + self.utcoffsets = deltas.data + pos = trans.searchsorted(values, side="right") - 1 + self.positions_arr = pos + self.positions = pos.data + self.use_pytz = typ == "pytz" + + cdef inline int64_t get_local_timestamp(self, int64_t utc_value, Py_ssize_t i): + cdef: + int64_t local_val + + if self.use_utc: + local_val = utc_value + elif self.use_tzlocal: + local_val = tz_convert_utc_to_tzlocal(utc_value, self.tz) + elif self.use_fixed: + local_val = utc_value + self.delta + else: + local_val = utc_value + self.utcoffsets[self.positions[i]] + return local_val + + cdef int64_t tz_localize_to_utc_single( int64_t val, tzinfo tz, object ambiguous=None, object nonexistent=None, ) except? -1: