From c621fbc2be7043b8e91c09e3ee7991223ad58613 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 23 Mar 2022 16:26:21 -0700 Subject: [PATCH 1/5] de-duplicate some conversion.pyx code --- pandas/_libs/tslibs/__init__.py | 4 +- pandas/_libs/tslibs/conversion.pyx | 56 ++---------------- pandas/_libs/tslibs/tzconversion.pxd | 15 ++++- pandas/_libs/tslibs/tzconversion.pyx | 79 +++++++++++++++++++++++--- pandas/tests/tslibs/test_conversion.py | 2 +- 5 files changed, 92 insertions(+), 64 deletions(-) diff --git a/pandas/_libs/tslibs/__init__.py b/pandas/_libs/tslibs/__init__.py index 11de4e60f202d..4c74959fee60d 100644 --- a/pandas/_libs/tslibs/__init__.py +++ b/pandas/_libs/tslibs/__init__.py @@ -55,7 +55,9 @@ ) from pandas._libs.tslibs.timestamps import Timestamp from pandas._libs.tslibs.timezones import tz_compare -from pandas._libs.tslibs.tzconversion import tz_convert_from_utc_single +from pandas._libs.tslibs.tzconversion import ( + py_tz_convert_from_utc_single as tz_convert_from_utc_single, +) from pandas._libs.tslibs.vectorized import ( dt64arr_to_periodarr, get_resolution, diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index 132d742b78e9c..9051dafd2e3f9 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -72,7 +72,9 @@ from pandas._libs.tslibs.nattype cimport ( ) from pandas._libs.tslibs.tzconversion cimport ( bisect_right_i8, + infer_dateutil_fold, localize_tzinfo_api, + tz_convert_from_utc_single, tz_localize_to_utc_single, ) @@ -522,15 +524,8 @@ cdef _TSObject _create_tsobject_tz_using_offset(npy_datetimestruct dts, # see PEP 495 https://www.python.org/dev/peps/pep-0495/#the-fold-attribute if is_utc(tz): pass - elif is_tzlocal(tz): - localize_tzinfo_api(obj.value, tz, &obj.fold) else: - trans, deltas, typ = get_dst_info(tz) - - if typ == 'dateutil': - tdata = cnp.PyArray_DATA(trans) - pos = bisect_right_i8(tdata, obj.value, trans.shape[0]) - 1 - obj.fold = _infer_tsobject_fold(obj, trans, deltas, pos) + tz_convert_from_utc_single(obj.value, tz, &obj.fold) # Keep the converter same as PyDateTime's dt = datetime(obj.dts.year, obj.dts.month, obj.dts.day, @@ -714,7 +709,7 @@ cdef inline void _localize_tso(_TSObject obj, tzinfo tz): local_val = obj.value + deltas[pos] # dateutil supports fold, so we infer fold from value - obj.fold = _infer_tsobject_fold(obj, trans, deltas, pos) + obj.fold = infer_dateutil_fold(obj.value, trans, deltas, pos) else: # All other cases have len(deltas) == 1. As of 2018-07-17 # (and 2022-03-07), all test cases that get here have @@ -726,49 +721,6 @@ cdef inline void _localize_tso(_TSObject obj, tzinfo tz): obj.tzinfo = tz -cdef inline bint _infer_tsobject_fold( - _TSObject obj, - const int64_t[:] trans, - const int64_t[:] deltas, - intp_t pos, -): - """ - Infer _TSObject fold property from value by assuming 0 and then setting - to 1 if necessary. - - Parameters - ---------- - obj : _TSObject - trans : ndarray[int64_t] - ndarray of offset transition points in nanoseconds since epoch. - deltas : int64_t[:] - array of offsets corresponding to transition points in trans. - pos : intp_t - Position of the last transition point before taking fold into account. - - Returns - ------- - bint - Due to daylight saving time, one wall clock time can occur twice - when shifting from summer to winter time; fold describes whether the - datetime-like corresponds to the first (0) or the second time (1) - the wall clock hits the ambiguous time - - References - ---------- - .. [1] "PEP 495 - Local Time Disambiguation" - https://www.python.org/dev/peps/pep-0495/#the-fold-attribute - """ - cdef: - bint fold = 0 - - if pos > 0: - fold_delta = deltas[pos - 1] - deltas[pos] - if obj.value - fold_delta < trans[pos]: - fold = 1 - - return fold - cdef inline datetime _localize_pydatetime(datetime dt, tzinfo tz): """ Take a datetime/Timestamp in UTC and localizes to timezone tz. diff --git a/pandas/_libs/tslibs/tzconversion.pxd b/pandas/_libs/tslibs/tzconversion.pxd index 136e62985995e..47cb4577950da 100644 --- a/pandas/_libs/tslibs/tzconversion.pxd +++ b/pandas/_libs/tslibs/tzconversion.pxd @@ -1,13 +1,24 @@ from cpython.datetime cimport tzinfo -from numpy cimport int64_t +from numpy cimport ( + int64_t, + intp_t, +) cdef int64_t localize_tzinfo_api( int64_t utc_val, tzinfo tz, bint* fold=* ) except? -1 -cpdef int64_t tz_convert_from_utc_single(int64_t val, tzinfo tz) +cdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz, bint* fold=?) cdef int64_t tz_localize_to_utc_single( int64_t val, tzinfo tz, object ambiguous=*, object nonexistent=* ) except? -1 cdef Py_ssize_t bisect_right_i8(int64_t *data, int64_t val, Py_ssize_t n) + + +cdef bint infer_dateutil_fold( + int64_t value, + const int64_t[::1] trans, + const int64_t[::1] deltas, + intp_t pos, +) diff --git a/pandas/_libs/tslibs/tzconversion.pyx b/pandas/_libs/tslibs/tzconversion.pyx index 9190585b2882d..c9b6093fa43a9 100644 --- a/pandas/_libs/tslibs/tzconversion.pyx +++ b/pandas/_libs/tslibs/tzconversion.pyx @@ -444,7 +444,13 @@ cdef int64_t localize_tzinfo_api( return _tz_localize_using_tzinfo_api(utc_val, tz, to_utc=False, fold=fold) -cpdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz): +def py_tz_convert_from_utc_single(int64_t utc_val, tzinfo tz): + # The 'bint* fold=NULL' in tz_convert_from_utc_single means we cannot + # make it cdef, so this is version exposed for testing from python. + return tz_convert_from_utc_single(utc_val, tz) + + +cdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz, bint* fold=NULL): """ Convert the val (in i8) from UTC to tz @@ -454,6 +460,7 @@ cpdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz): ---------- utc_val : int64 tz : tzinfo + fold : bint*, default NULL Returns ------- @@ -473,15 +480,26 @@ cpdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz): return utc_val elif is_tzlocal(tz): return utc_val + _tz_localize_using_tzinfo_api(utc_val, tz, to_utc=False) - elif is_fixed_offset(tz): - _, deltas, _ = get_dst_info(tz) - delta = deltas[0] - return utc_val + delta else: - trans, deltas, _ = get_dst_info(tz) + trans, deltas, typ = get_dst_info(tz) tdata = cnp.PyArray_DATA(trans) - pos = bisect_right_i8(tdata, utc_val, trans.shape[0]) - 1 - return utc_val + deltas[pos] + + if typ == "dateutil": + pos = bisect_right_i8(tdata, utc_val, trans.shape[0]) - 1 + + if fold is not NULL: + fold[0] = infer_dateutil_fold(utc_val, trans, deltas, pos) + return utc_val + deltas[pos] + + elif typ == "pytz": + pos = bisect_right_i8(tdata, utc_val, trans.shape[0]) - 1 + return utc_val + deltas[pos] + + else: + # All other cases have len(deltas) == 1. As of 2018-07-17 + # (and 2022-03-07), all test cases that get here have + # is_fixed_offset(tz). + return utc_val + deltas[0] def tz_convert_from_utc(const int64_t[:] vals, tzinfo tz): @@ -632,3 +650,48 @@ cdef int64_t _tz_localize_using_tzinfo_api( td = tz.utcoffset(dt) delta = int(td.total_seconds() * 1_000_000_000) return delta + + +# NB: this relies on dateutil internals and is subject to change. +cdef bint infer_dateutil_fold( + int64_t value, + const int64_t[::1] trans, + const int64_t[::1] deltas, + intp_t pos, +): + """ + Infer _TSObject fold property from value by assuming 0 and then setting + to 1 if necessary. + + Parameters + ---------- + value : int64_t + trans : ndarray[int64_t] + ndarray of offset transition points in nanoseconds since epoch. + deltas : int64_t[:] + array of offsets corresponding to transition points in trans. + pos : intp_t + Position of the last transition point before taking fold into account. + + Returns + ------- + bint + Due to daylight saving time, one wall clock time can occur twice + when shifting from summer to winter time; fold describes whether the + datetime-like corresponds to the first (0) or the second time (1) + the wall clock hits the ambiguous time + + References + ---------- + .. [1] "PEP 495 - Local Time Disambiguation" + https://www.python.org/dev/peps/pep-0495/#the-fold-attribute + """ + cdef: + bint fold = 0 + + if pos > 0: + fold_delta = deltas[pos - 1] - deltas[pos] + if value - fold_delta < trans[pos]: + fold = 1 + + return fold diff --git a/pandas/tests/tslibs/test_conversion.py b/pandas/tests/tslibs/test_conversion.py index d0864ae8e1b7b..a790b2617783f 100644 --- a/pandas/tests/tslibs/test_conversion.py +++ b/pandas/tests/tslibs/test_conversion.py @@ -21,7 +21,7 @@ def _compare_utc_to_local(tz_didx): def f(x): - return tzconversion.tz_convert_from_utc_single(x, tz_didx.tz) + return tzconversion.py_tz_convert_from_utc_single(x, tz_didx.tz) result = tzconversion.tz_convert_from_utc(tz_didx.asi8, tz_didx.tz) expected = np.vectorize(f)(tz_didx.asi8) From 9b3f110e5aca31a36dd1d391b17c938add1f5c77 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 23 Mar 2022 19:13:29 -0700 Subject: [PATCH 2/5] Use outpos in tz_convert_from_utc_single --- pandas/_libs/tslibs/conversion.pyx | 37 +++++++--------------------- pandas/_libs/tslibs/tzconversion.pxd | 2 +- pandas/_libs/tslibs/tzconversion.pyx | 6 ++++- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index 9051dafd2e3f9..57f831e58c382 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -525,6 +525,9 @@ cdef _TSObject _create_tsobject_tz_using_offset(npy_datetimestruct dts, if is_utc(tz): pass else: + # TODO: are we doing unnecessary work for pytz/fixed case? can we + # use the local_val returned from this call to make some + # of the rest of the function unnecessary? tz_convert_from_utc_single(obj.value, tz, &obj.fold) # Keep the converter same as PyDateTime's @@ -677,6 +680,7 @@ cdef inline void _localize_tso(_TSObject obj, tzinfo tz): int64_t[::1] deltas int64_t local_val int64_t* tdata + intp_t outpos = -1 Py_ssize_t pos, ntrans str typ @@ -686,35 +690,12 @@ cdef inline void _localize_tso(_TSObject obj, tzinfo tz): pass elif obj.value == NPY_NAT: pass - elif is_tzlocal(tz): - local_val = obj.value + localize_tzinfo_api(obj.value, tz, &obj.fold) - dt64_to_dtstruct(local_val, &obj.dts) else: - # Adjust datetime64 timestamp, recompute datetimestruct - trans, deltas, typ = get_dst_info(tz) - ntrans = trans.shape[0] - - if typ == "pytz": - # i.e. treat_tz_as_pytz(tz) - tdata = cnp.PyArray_DATA(trans) - pos = bisect_right_i8(tdata, obj.value, ntrans) - 1 - local_val = obj.value + deltas[pos] - - # find right representation of dst etc in pytz timezone - tz = tz._tzinfos[tz._transition_info[pos]] - elif typ == "dateutil": - # i.e. treat_tz_as_dateutil(tz) - tdata = cnp.PyArray_DATA(trans) - pos = bisect_right_i8(tdata, obj.value, ntrans) - 1 - local_val = obj.value + deltas[pos] - - # dateutil supports fold, so we infer fold from value - obj.fold = infer_dateutil_fold(obj.value, trans, deltas, pos) - else: - # All other cases have len(deltas) == 1. As of 2018-07-17 - # (and 2022-03-07), all test cases that get here have - # is_fixed_offset(tz). - local_val = obj.value + deltas[0] + local_val = tz_convert_from_utc_single(obj.value, tz, &obj.fold, &outpos) + + if outpos != -1: + # infer we went through a pytz path + tz = tz._tzinfos[tz._transition_info[outpos]] dt64_to_dtstruct(local_val, &obj.dts) diff --git a/pandas/_libs/tslibs/tzconversion.pxd b/pandas/_libs/tslibs/tzconversion.pxd index 47cb4577950da..f00faef43a4b7 100644 --- a/pandas/_libs/tslibs/tzconversion.pxd +++ b/pandas/_libs/tslibs/tzconversion.pxd @@ -8,7 +8,7 @@ from numpy cimport ( cdef int64_t localize_tzinfo_api( int64_t utc_val, tzinfo tz, bint* fold=* ) except? -1 -cdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz, bint* fold=?) +cdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz, bint* fold=?, intp_t* outpos=?) cdef int64_t tz_localize_to_utc_single( int64_t val, tzinfo tz, object ambiguous=*, object nonexistent=* ) except? -1 diff --git a/pandas/_libs/tslibs/tzconversion.pyx b/pandas/_libs/tslibs/tzconversion.pyx index c9b6093fa43a9..a8d44c5b99d79 100644 --- a/pandas/_libs/tslibs/tzconversion.pyx +++ b/pandas/_libs/tslibs/tzconversion.pyx @@ -450,7 +450,7 @@ def py_tz_convert_from_utc_single(int64_t utc_val, tzinfo tz): return tz_convert_from_utc_single(utc_val, tz) -cdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz, bint* fold=NULL): +cdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz, bint* fold=NULL, intp_t* outpos=NULL): """ Convert the val (in i8) from UTC to tz @@ -461,6 +461,7 @@ cdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz, bint* fold=N utc_val : int64 tz : tzinfo fold : bint*, default NULL + outpos : intp_t*, default NULL Returns ------- @@ -493,6 +494,9 @@ cdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz, bint* fold=N elif typ == "pytz": pos = bisect_right_i8(tdata, utc_val, trans.shape[0]) - 1 + + if outpos is not NULL: + outpos[0] = pos return utc_val + deltas[pos] else: From d4fec4ccd68f6ecdf115d86a7fb91d31dee853af Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 24 Mar 2022 10:55:24 -0700 Subject: [PATCH 3/5] perf troubleshoot --- pandas/_libs/tslibs/conversion.pyx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index 57f831e58c382..86b562ed0b3bb 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -524,11 +524,21 @@ cdef _TSObject _create_tsobject_tz_using_offset(npy_datetimestruct dts, # see PEP 495 https://www.python.org/dev/peps/pep-0495/#the-fold-attribute if is_utc(tz): pass + elif is_tzlocal(tz): + localize_tzinfo_api(obj.value, tz, &obj.fold) else: - # TODO: are we doing unnecessary work for pytz/fixed case? can we - # use the local_val returned from this call to make some - # of the rest of the function unnecessary? - tz_convert_from_utc_single(obj.value, tz, &obj.fold) + trans, deltas, typ = get_dst_info(tz) + + if typ == 'dateutil': + tdata = cnp.PyArray_DATA(trans) + pos = bisect_right_i8(tdata, obj.value, trans.shape[0]) - 1 + obj.fold = infer_dateutil_fold(obj.value, trans, deltas, pos) + + #else: + # # TODO: are we doing unnecessary work for pytz/fixed case? can we + # # use the local_val returned from this call to make some + # # of the rest of the function unnecessary? + # tz_convert_from_utc_single(obj.value, tz, &obj.fold) # Keep the converter same as PyDateTime's dt = datetime(obj.dts.year, obj.dts.month, obj.dts.day, From 83c5be2c43dd3a5e0f2c8a707126387f360d8a79 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 26 Mar 2022 20:49:31 -0700 Subject: [PATCH 4/5] REF: re-use tz_convert_from_utc_single in _localize_tso --- pandas/_libs/tslibs/conversion.pyx | 9 +-------- pandas/_libs/tslibs/tzconversion.pxd | 2 +- pandas/_libs/tslibs/tzconversion.pyi | 4 +++- pandas/_libs/tslibs/tzconversion.pyx | 11 +++++++++-- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index 86b562ed0b3bb..4cde5f51c643e 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -534,12 +534,6 @@ cdef _TSObject _create_tsobject_tz_using_offset(npy_datetimestruct dts, pos = bisect_right_i8(tdata, obj.value, trans.shape[0]) - 1 obj.fold = infer_dateutil_fold(obj.value, trans, deltas, pos) - #else: - # # TODO: are we doing unnecessary work for pytz/fixed case? can we - # # use the local_val returned from this call to make some - # # of the rest of the function unnecessary? - # tz_convert_from_utc_single(obj.value, tz, &obj.fold) - # Keep the converter same as PyDateTime's dt = datetime(obj.dts.year, obj.dts.month, obj.dts.day, obj.dts.hour, obj.dts.min, obj.dts.sec, @@ -690,8 +684,7 @@ cdef inline void _localize_tso(_TSObject obj, tzinfo tz): int64_t[::1] deltas int64_t local_val int64_t* tdata - intp_t outpos = -1 - Py_ssize_t pos, ntrans + Py_ssize_t pos, ntrans, outpos = -1 str typ assert obj.tzinfo is None diff --git a/pandas/_libs/tslibs/tzconversion.pxd b/pandas/_libs/tslibs/tzconversion.pxd index f00faef43a4b7..8e9ebbf553319 100644 --- a/pandas/_libs/tslibs/tzconversion.pxd +++ b/pandas/_libs/tslibs/tzconversion.pxd @@ -8,7 +8,7 @@ from numpy cimport ( cdef int64_t localize_tzinfo_api( int64_t utc_val, tzinfo tz, bint* fold=* ) except? -1 -cdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz, bint* fold=?, intp_t* outpos=?) +cdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz, bint* fold=?, Py_ssize_t* outpos=?) cdef int64_t tz_localize_to_utc_single( int64_t val, tzinfo tz, object ambiguous=*, object nonexistent=* ) except? -1 diff --git a/pandas/_libs/tslibs/tzconversion.pyi b/pandas/_libs/tslibs/tzconversion.pyi index e1a0263cf59ef..5e513eefdca15 100644 --- a/pandas/_libs/tslibs/tzconversion.pyi +++ b/pandas/_libs/tslibs/tzconversion.pyi @@ -12,7 +12,9 @@ def tz_convert_from_utc( vals: npt.NDArray[np.int64], # const int64_t[:] tz: tzinfo, ) -> npt.NDArray[np.int64]: ... -def tz_convert_from_utc_single(val: np.int64, tz: tzinfo) -> np.int64: ... + +# py_tz_convert_from_utc_single exposed for testing +def py_tz_convert_from_utc_single(val: np.int64, tz: tzinfo) -> np.int64: ... def tz_localize_to_utc( vals: npt.NDArray[np.int64], tz: tzinfo | None, diff --git a/pandas/_libs/tslibs/tzconversion.pyx b/pandas/_libs/tslibs/tzconversion.pyx index a8d44c5b99d79..815313f1202c1 100644 --- a/pandas/_libs/tslibs/tzconversion.pyx +++ b/pandas/_libs/tslibs/tzconversion.pyx @@ -450,7 +450,12 @@ def py_tz_convert_from_utc_single(int64_t utc_val, tzinfo tz): return tz_convert_from_utc_single(utc_val, tz) -cdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz, bint* fold=NULL, intp_t* outpos=NULL): +cdef int64_t tz_convert_from_utc_single( + int64_t utc_val, + tzinfo tz, + bint* fold=NULL, + Py_ssize_t* outpos=NULL, +): """ Convert the val (in i8) from UTC to tz @@ -461,7 +466,7 @@ cdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz, bint* fold=N utc_val : int64 tz : tzinfo fold : bint*, default NULL - outpos : intp_t*, default NULL + outpos : Py_ssize_t*, default NULL Returns ------- @@ -495,6 +500,8 @@ cdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz, bint* fold=N elif typ == "pytz": pos = bisect_right_i8(tdata, utc_val, trans.shape[0]) - 1 + # We need to get 'pos' back to the caller so it can pick the + # correct "standardized" tzinfo objecg. if outpos is not NULL: outpos[0] = pos return utc_val + deltas[pos] From 5f365a2d3294f2eb90b89ba98449f0341d7c9a17 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 28 Mar 2022 19:10:13 -0700 Subject: [PATCH 5/5] fix xpass maybe --- pandas/_libs/tslibs/tzconversion.pxd | 4 +++- pandas/_libs/tslibs/tzconversion.pyx | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pandas/_libs/tslibs/tzconversion.pxd b/pandas/_libs/tslibs/tzconversion.pxd index 3fafbc00656cc..ce7541fe1e74e 100644 --- a/pandas/_libs/tslibs/tzconversion.pxd +++ b/pandas/_libs/tslibs/tzconversion.pxd @@ -8,7 +8,9 @@ from numpy cimport ( cdef int64_t localize_tzinfo_api( int64_t utc_val, tzinfo tz, bint* fold=* ) except? -1 -cdef int64_t tz_convert_from_utc_single(int64_t utc_val, tzinfo tz, bint* fold=?, Py_ssize_t* outpos=?) +cdef int64_t tz_convert_from_utc_single( + int64_t utc_val, tzinfo tz, bint* fold=?, Py_ssize_t* outpos=? +) except? -1 cdef int64_t tz_localize_to_utc_single( int64_t val, tzinfo tz, object ambiguous=*, object nonexistent=* ) except? -1 diff --git a/pandas/_libs/tslibs/tzconversion.pyx b/pandas/_libs/tslibs/tzconversion.pyx index c769976e056e8..afcfe94a695bb 100644 --- a/pandas/_libs/tslibs/tzconversion.pyx +++ b/pandas/_libs/tslibs/tzconversion.pyx @@ -455,7 +455,7 @@ cdef int64_t tz_convert_from_utc_single( tzinfo tz, bint* fold=NULL, Py_ssize_t* outpos=NULL, -): +) except? -1: """ Convert the val (in i8) from UTC to tz