Skip to content

Commit ea90058

Browse files
jbrockmendelvictor
authored and
victor
committed
implement _tz_convert_dst for de-duplication (pandas-dev#21730)
1 parent cf60236 commit ea90058

File tree

1 file changed

+74
-67
lines changed

1 file changed

+74
-67
lines changed

pandas/_libs/tslibs/conversion.pyx

+74-67
Original file line numberDiff line numberDiff line change
@@ -608,9 +608,66 @@ cpdef inline datetime localize_pydatetime(datetime dt, object tz):
608608
# ----------------------------------------------------------------------
609609
# Timezone Conversion
610610

611+
cdef inline int64_t[:] _tz_convert_dst(ndarray[int64_t] values, tzinfo tz,
612+
bint to_utc=True):
613+
"""
614+
tz_convert for non-UTC non-tzlocal cases where we have to check
615+
DST transitions pointwise.
616+
617+
Parameters
618+
----------
619+
values : ndarray[int64_t]
620+
tz : tzinfo
621+
to_utc : bool
622+
True if converting _to_ UTC, False if converting _from_ utc
623+
624+
Returns
625+
-------
626+
result : ndarray[int64_t]
627+
"""
628+
cdef:
629+
Py_ssize_t n = len(values)
630+
Py_ssize_t i, j, pos
631+
ndarray[int64_t] result = np.empty(n, dtype=np.int64)
632+
ndarray[int64_t] tt, trans, deltas
633+
ndarray[Py_ssize_t] posn
634+
int64_t v
635+
636+
trans, deltas, typ = get_dst_info(tz)
637+
if not to_utc:
638+
# We add `offset` below instead of subtracting it
639+
deltas = -1 * deltas
640+
641+
tt = values[values != NPY_NAT]
642+
if not len(tt):
643+
# if all NaT, return all NaT
644+
return values
645+
646+
posn = trans.searchsorted(tt, side='right')
647+
648+
j = 0
649+
for i in range(n):
650+
v = values[i]
651+
if v == NPY_NAT:
652+
result[i] = v
653+
else:
654+
pos = posn[j] - 1
655+
j += 1
656+
if pos < 0:
657+
raise ValueError('First time before start of DST info')
658+
result[i] = v - deltas[pos]
659+
660+
return result
661+
662+
611663
cdef inline int64_t _tz_convert_tzlocal_utc(int64_t val, tzinfo tz,
612664
bint to_utc=True):
613665
"""
666+
Convert the i8 representation of a datetime from a tzlocal timezone to
667+
UTC, or vice-versa.
668+
669+
Private, not intended for use outside of tslibs.conversion
670+
614671
Parameters
615672
----------
616673
val : int64_t
@@ -672,6 +729,7 @@ cpdef int64_t tz_convert_single(int64_t val, object tz1, object tz2):
672729
Py_ssize_t pos
673730
int64_t v, offset, utc_date
674731
pandas_datetimestruct dts
732+
ndarray[int64_t] arr # TODO: Is there a lighter-weight way to do this?
675733

676734
# See GH#17734 We should always be converting either from UTC or to UTC
677735
assert (is_utc(tz1) or tz1 == 'UTC') or (is_utc(tz2) or tz2 == 'UTC')
@@ -683,29 +741,23 @@ cpdef int64_t tz_convert_single(int64_t val, object tz1, object tz2):
683741
if is_tzlocal(tz1):
684742
utc_date = _tz_convert_tzlocal_utc(val, tz1, to_utc=True)
685743
elif get_timezone(tz1) != 'UTC':
686-
trans, deltas, typ = get_dst_info(tz1)
687-
pos = trans.searchsorted(val, side='right') - 1
688-
if pos < 0:
689-
raise ValueError('First time before start of DST info')
690-
offset = deltas[pos]
691-
utc_date = val - offset
744+
arr = np.array([val])
745+
utc_date = _tz_convert_dst(arr, tz1, to_utc=True)[0]
692746
else:
693747
utc_date = val
694748

695749
if get_timezone(tz2) == 'UTC':
696750
return utc_date
697751
elif is_tzlocal(tz2):
698752
return _tz_convert_tzlocal_utc(utc_date, tz2, to_utc=False)
699-
700-
# Convert UTC to other timezone
701-
trans, deltas, typ = get_dst_info(tz2)
702-
703-
pos = trans.searchsorted(utc_date, side='right') - 1
704-
if pos < 0:
705-
raise ValueError('First time before start of DST info')
706-
707-
offset = deltas[pos]
708-
return utc_date + offset
753+
else:
754+
# Convert UTC to other timezone
755+
arr = np.array([utc_date])
756+
# Note: at least with cython 0.28.3, doing a looking `[0]` in the next
757+
# line is sensitive to the declared return type of _tz_convert_dst;
758+
# if it is declared as returning ndarray[int64_t], a compile-time error
759+
# is raised.
760+
return _tz_convert_dst(arr, tz2, to_utc=False)[0]
709761

710762

711763
@cython.boundscheck(False)
@@ -746,70 +798,25 @@ def tz_convert(ndarray[int64_t] vals, object tz1, object tz2):
746798
else:
747799
utc_dates[i] = _tz_convert_tzlocal_utc(v, tz1, to_utc=True)
748800
else:
749-
trans, deltas, typ = get_dst_info(tz1)
750-
751-
# all-NaT
752-
tt = vals[vals != NPY_NAT]
753-
if not len(tt):
754-
return vals
755-
756-
posn = trans.searchsorted(tt, side='right')
757-
j = 0
758-
for i in range(n):
759-
v = vals[i]
760-
if v == NPY_NAT:
761-
utc_dates[i] = NPY_NAT
762-
else:
763-
pos = posn[j] - 1
764-
j = j + 1
765-
if pos < 0:
766-
raise ValueError('First time before start of DST info')
767-
offset = deltas[pos]
768-
utc_dates[i] = v - offset
801+
utc_dates = np.array(_tz_convert_dst(vals, tz1, to_utc=True))
769802
else:
770803
utc_dates = vals
771804

772805
if get_timezone(tz2) == 'UTC':
773806
return utc_dates
774807

775-
result = np.zeros(n, dtype=np.int64)
776-
if is_tzlocal(tz2):
808+
elif is_tzlocal(tz2):
809+
result = np.zeros(n, dtype=np.int64)
777810
for i in range(n):
778811
v = utc_dates[i]
779812
if v == NPY_NAT:
780813
result[i] = NPY_NAT
781814
else:
782815
result[i] = _tz_convert_tzlocal_utc(v, tz2, to_utc=False)
783816
return result
784-
785-
# Convert UTC to other timezone
786-
trans, deltas, typ = get_dst_info(tz2)
787-
788-
# use first non-NaT element
789-
# if all-NaT, return all-NaT
790-
if (result == NPY_NAT).all():
791-
return result
792-
793-
# if all NaT, return all NaT
794-
tt = utc_dates[utc_dates != NPY_NAT]
795-
if not len(tt):
796-
return utc_dates
797-
798-
posn = trans.searchsorted(tt, side='right')
799-
800-
j = 0
801-
for i in range(n):
802-
v = utc_dates[i]
803-
if vals[i] == NPY_NAT:
804-
result[i] = vals[i]
805-
else:
806-
pos = posn[j] - 1
807-
j = j + 1
808-
if pos < 0:
809-
raise ValueError('First time before start of DST info')
810-
offset = deltas[pos]
811-
result[i] = v + offset
812-
return result
817+
else:
818+
# Convert UTC to other timezone
819+
return np.array(_tz_convert_dst(utc_dates, tz2, to_utc=False))
813820

814821

815822
# TODO: cdef scalar version to call from convert_str_to_tsobject

0 commit comments

Comments
 (0)