@@ -25,7 +25,11 @@ cnp.import_array()
25
25
from pandas._libs.properties import cache_readonly
26
26
27
27
from pandas._libs.tslibs cimport util
28
- from pandas._libs.tslibs.util cimport is_integer_object, is_datetime64_object
28
+ from pandas._libs.tslibs.util cimport (
29
+ is_integer_object,
30
+ is_datetime64_object,
31
+ is_float_object,
32
+ )
29
33
30
34
from pandas._libs.tslibs.base cimport ABCTimestamp
31
35
@@ -743,6 +747,25 @@ cdef class Tick(SingleConstructorOffset):
743
747
" Tick offset with `normalize=True` are not allowed."
744
748
)
745
749
750
+ # FIXME: Without making this cpdef, we get AttributeError when calling
751
+ # from __mul__
752
+ cpdef Tick _next_higher_resolution(Tick self ):
753
+ if type (self ) is Day:
754
+ return Hour(self .n * 24 )
755
+ if type (self ) is Hour:
756
+ return Minute(self .n * 60 )
757
+ if type (self ) is Minute:
758
+ return Second(self .n * 60 )
759
+ if type (self ) is Second:
760
+ return Milli(self .n * 1000 )
761
+ if type (self ) is Milli:
762
+ return Micro(self .n * 1000 )
763
+ if type (self ) is Micro:
764
+ return Nano(self .n * 1000 )
765
+ raise NotImplementedError (type (self ))
766
+
767
+ # --------------------------------------------------------------------
768
+
746
769
def _repr_attrs (self ) -> str:
747
770
# Since cdef classes have no __dict__ , we need to override
748
771
return ""
@@ -791,6 +814,21 @@ cdef class Tick(SingleConstructorOffset):
791
814
def __gt__ (self , other ):
792
815
return self .delta.__gt__ (other)
793
816
817
+ def __mul__ (self , other ):
818
+ if not isinstance (self , Tick):
819
+ # cython semantics, this is __rmul__
820
+ return other.__mul__ (self )
821
+ if is_float_object(other):
822
+ n = other * self .n
823
+ # If the new `n` is an integer, we can represent it using the
824
+ # same Tick subclass as self, otherwise we need to move up
825
+ # to a higher-resolution subclass
826
+ if np.isclose(n % 1 , 0 ):
827
+ return type (self )(int (n))
828
+ new_self = self ._next_higher_resolution()
829
+ return new_self * other
830
+ return BaseOffset.__mul__ (self , other)
831
+
794
832
def __truediv__ (self , other ):
795
833
if not isinstance (self , Tick):
796
834
# cython semantics mean the args are sometimes swapped
@@ -3563,6 +3601,9 @@ cpdef to_offset(freq):
3563
3601
>>> to_offset(Hour())
3564
3602
<Hour>
3565
3603
"""
3604
+ # TODO: avoid runtime imports
3605
+ from pandas._libs.tslibs.timedeltas import Timedelta
3606
+
3566
3607
if freq is None :
3567
3608
return None
3568
3609
@@ -3589,7 +3630,9 @@ cpdef to_offset(freq):
3589
3630
if split[- 1 ] != " " and not split[- 1 ].isspace():
3590
3631
# the last element must be blank
3591
3632
raise ValueError (" last element must be blank" )
3592
- for sep, stride, name in zip (split[0 ::4 ], split[1 ::4 ], split[2 ::4 ]):
3633
+
3634
+ tups = zip (split[0 ::4 ], split[1 ::4 ], split[2 ::4 ])
3635
+ for n, (sep, stride, name) in enumerate (tups):
3593
3636
if sep != " " and not sep.isspace():
3594
3637
raise ValueError (" separator must be spaces" )
3595
3638
prefix = _lite_rule_alias.get(name) or name
@@ -3598,16 +3641,22 @@ cpdef to_offset(freq):
3598
3641
if not stride:
3599
3642
stride = 1
3600
3643
3601
- # TODO: avoid runtime import
3602
- from .resolution import Resolution, reso_str_bump_map
3644
+ if prefix in {" D" , " H" , " T" , " S" , " L" , " U" , " N" }:
3645
+ # For these prefixes, we have something like "3H" or
3646
+ # "2.5T", so we can construct a Timedelta with the
3647
+ # matching unit and get our offset from delta_to_tick
3648
+ td = Timedelta(1 , unit = prefix)
3649
+ off = delta_to_tick(td)
3650
+ offset = off * float (stride)
3651
+ if n != 0 :
3652
+ # If n==0, then stride_sign is already incorporated
3653
+ # into the offset
3654
+ offset *= stride_sign
3655
+ else :
3656
+ stride = int (stride)
3657
+ offset = _get_offset(name)
3658
+ offset = offset * int (np.fabs(stride) * stride_sign)
3603
3659
3604
- if prefix in reso_str_bump_map:
3605
- stride, name = Resolution.get_stride_from_decimal(
3606
- float (stride), prefix
3607
- )
3608
- stride = int (stride)
3609
- offset = _get_offset(name)
3610
- offset = offset * int (np.fabs(stride) * stride_sign)
3611
3660
if delta is None :
3612
3661
delta = offset
3613
3662
else :
0 commit comments