@@ -608,9 +608,66 @@ cpdef inline datetime localize_pydatetime(datetime dt, object tz):
608
608
# ----------------------------------------------------------------------
609
609
# Timezone Conversion
610
610
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
+
611
663
cdef inline int64_t _tz_convert_tzlocal_utc(int64_t val, tzinfo tz,
612
664
bint to_utc = True ):
613
665
"""
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
+
614
671
Parameters
615
672
----------
616
673
val : int64_t
@@ -672,6 +729,7 @@ cpdef int64_t tz_convert_single(int64_t val, object tz1, object tz2):
672
729
Py_ssize_t pos
673
730
int64_t v, offset, utc_date
674
731
pandas_datetimestruct dts
732
+ ndarray[int64_t] arr # TODO: Is there a lighter-weight way to do this?
675
733
676
734
# See GH#17734 We should always be converting either from UTC or to UTC
677
735
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):
683
741
if is_tzlocal(tz1):
684
742
utc_date = _tz_convert_tzlocal_utc(val, tz1, to_utc = True )
685
743
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 ]
692
746
else :
693
747
utc_date = val
694
748
695
749
if get_timezone(tz2) == ' UTC' :
696
750
return utc_date
697
751
elif is_tzlocal(tz2):
698
752
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 ]
709
761
710
762
711
763
@ cython.boundscheck (False )
@@ -746,70 +798,25 @@ def tz_convert(ndarray[int64_t] vals, object tz1, object tz2):
746
798
else :
747
799
utc_dates[i] = _tz_convert_tzlocal_utc(v, tz1, to_utc = True )
748
800
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 ))
769
802
else :
770
803
utc_dates = vals
771
804
772
805
if get_timezone(tz2) == ' UTC' :
773
806
return utc_dates
774
807
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)
777
810
for i in range (n):
778
811
v = utc_dates[i]
779
812
if v == NPY_NAT:
780
813
result[i] = NPY_NAT
781
814
else :
782
815
result[i] = _tz_convert_tzlocal_utc(v, tz2, to_utc = False )
783
816
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 ))
813
820
814
821
815
822
# TODO: cdef scalar version to call from convert_str_to_tsobject
0 commit comments