@@ -32,6 +32,8 @@ cnp.import_array()
32
32
33
33
# TODO: formalize having _libs.properties "above" tslibs in the dependency structure
34
34
35
+ from typing import ClassVar
36
+
35
37
from pandas._libs.properties import cache_readonly
36
38
37
39
from pandas._libs.tslibs cimport util
@@ -2524,8 +2526,7 @@ cdef class YearOffset(SingleConstructorOffset):
2524
2526
"""
2525
2527
_attributes = tuple ([" n" , " normalize" , " month" ])
2526
2528
2527
- # FIXME(cython#4446): python annotation here gives compile-time errors
2528
- # _default_month: int
2529
+ _default_month: ClassVar[int ]
2529
2530
2530
2531
cdef readonly:
2531
2532
int month
@@ -2788,9 +2789,8 @@ cdef class QuarterOffset(SingleConstructorOffset):
2788
2789
# point. Also apply_index, is_on_offset, rule_code if
2789
2790
# startingMonth vs month attr names are resolved
2790
2791
2791
- # FIXME(cython#4446): python annotation here gives compile-time errors
2792
- # _default_starting_month: int
2793
- # _from_name_starting_month: int
2792
+ _default_starting_month: ClassVar[int ]
2793
+ _from_name_starting_month: ClassVar[int ]
2794
2794
2795
2795
cdef readonly:
2796
2796
int startingMonth
@@ -3011,6 +3011,227 @@ cdef class QuarterBegin(QuarterOffset):
3011
3011
_day_opt = " start"
3012
3012
3013
3013
3014
+ # ----------------------------------------------------------------------
3015
+ # HalfYear-Based Offset Classes
3016
+
3017
+ cdef class HalfYearOffset(SingleConstructorOffset):
3018
+ _attributes = tuple ([" n" , " normalize" , " startingMonth" ])
3019
+ # TODO: Consider combining HalfYearOffset, QuarterOffset and YearOffset
3020
+
3021
+ _default_starting_month: ClassVar[int ]
3022
+ _from_name_starting_month: ClassVar[int ]
3023
+
3024
+ cdef readonly:
3025
+ int startingMonth
3026
+
3027
+ def __init__ (self , n = 1 , normalize = False , startingMonth = None ):
3028
+ BaseOffset.__init__ (self , n, normalize)
3029
+
3030
+ if startingMonth is None :
3031
+ startingMonth = self ._default_starting_month
3032
+ self .startingMonth = startingMonth
3033
+
3034
+ cpdef __setstate__(self , state):
3035
+ self .startingMonth = state.pop(" startingMonth" )
3036
+ self .n = state.pop(" n" )
3037
+ self .normalize = state.pop(" normalize" )
3038
+
3039
+ @classmethod
3040
+ def _from_name (cls , suffix = None ):
3041
+ kwargs = {}
3042
+ if suffix:
3043
+ kwargs[" startingMonth" ] = MONTH_TO_CAL_NUM[suffix]
3044
+ else :
3045
+ if cls ._from_name_starting_month is not None :
3046
+ kwargs[" startingMonth" ] = cls ._from_name_starting_month
3047
+ return cls (** kwargs)
3048
+
3049
+ @property
3050
+ def rule_code (self ) -> str:
3051
+ month = MONTH_ALIASES[self .startingMonth]
3052
+ return f"{self._prefix}-{month}"
3053
+
3054
+ def is_on_offset(self , dt: datetime ) -> bool:
3055
+ if self.normalize and not _is_normalized(dt ):
3056
+ return False
3057
+ mod_month = (dt.month - self .startingMonth) % 6
3058
+ return mod_month == 0 and dt.day == self ._get_offset_day(dt)
3059
+
3060
+ @apply_wraps
3061
+ def _apply (self , other: datetime ) -> datetime:
3062
+ # months_since: find the calendar half containing other.month ,
3063
+ # e.g. if other.month == 8, the calendar half is [Jul , Aug , Sep , ..., Dec].
3064
+ # Then find the month in that half containing an is_on_offset date for
3065
+ # self. `months_since` is the number of months to shift other.month
3066
+ # to get to this on-offset month.
3067
+ months_since = other.month % 6 - self .startingMonth % 6
3068
+ hlvs = roll_qtrday(
3069
+ other, self .n, self .startingMonth, day_opt = self ._day_opt, modby = 6
3070
+ )
3071
+ months = hlvs * 6 - months_since
3072
+ return shift_month(other , months , self._day_opt )
3073
+
3074
+ def _apply_array(self , dtarr: np.ndarray ) -> np.ndarray:
3075
+ reso = get_unit_from_dtype(dtarr.dtype)
3076
+ shifted = shift_quarters(
3077
+ dtarr.view(" i8" ),
3078
+ self .n,
3079
+ self .startingMonth,
3080
+ self ._day_opt,
3081
+ modby = 6 ,
3082
+ reso = reso,
3083
+ )
3084
+ return shifted
3085
+
3086
+
3087
+ cdef class BHalfYearEnd(HalfYearOffset ):
3088
+ """
3089
+ DateOffset increments between the last business day of each half-year.
3090
+
3091
+ startingMonth = 1 corresponds to dates like 1/31/2007, 7/31/2007, ...
3092
+ startingMonth = 2 corresponds to dates like 2/28/2007, 8/31/2007, ...
3093
+ startingMonth = 6 corresponds to dates like 6/30/2007, 12/31/2007, ...
3094
+
3095
+ Attributes
3096
+ ----------
3097
+ n : int, default 1
3098
+ The number of half-years represented.
3099
+ normalize : bool, default False
3100
+ Normalize start/end dates to midnight before generating date range.
3101
+ startingMonth : int, default 6
3102
+ A specific integer for the month of the year from which we start half-years.
3103
+
3104
+ See Also
3105
+ --------
3106
+ :class:`~pandas.tseries.offsets.DateOffset` : Standard kind of date increment.
3107
+
3108
+ Examples
3109
+ --------
3110
+ >>> from pandas.tseries.offsets import BHalfYearEnd
3111
+ >>> ts = pd.Timestamp('2020-05-24 05:01:15')
3112
+ >>> ts + BHalfYearEnd()
3113
+ Timestamp('2020-06-30 05:01:15')
3114
+ >>> ts + BHalfYearEnd(2)
3115
+ Timestamp('2020-12-31 05:01:15')
3116
+ >>> ts + BHalfYearEnd(1, startingMonth=2)
3117
+ Timestamp('2020-08-31 05:01:15')
3118
+ >>> ts + BHalfYearEnd(startingMonth=2)
3119
+ Timestamp('2020-08-31 05:01:15')
3120
+ """
3121
+ _output_name = " BusinessHalfYearEnd"
3122
+ _default_starting_month = 6
3123
+ _from_name_starting_month = 12
3124
+ _prefix = " BHYE"
3125
+ _day_opt = " business_end"
3126
+
3127
+
3128
+ cdef class BHalfYearBegin(HalfYearOffset):
3129
+ """
3130
+ DateOffset increments between the first business day of each half-year.
3131
+
3132
+ startingMonth = 1 corresponds to dates like 1/01/2007, 7/01/2007, ...
3133
+ startingMonth = 2 corresponds to dates like 2/01/2007, 8/01/2007, ...
3134
+ startingMonth = 3 corresponds to dates like 3/01/2007, 9/01/2007, ...
3135
+
3136
+ Attributes
3137
+ ----------
3138
+ n : int, default 1
3139
+ The number of half-years represented.
3140
+ normalize : bool, default False
3141
+ Normalize start/end dates to midnight before generating date range.
3142
+ startingMonth : int, default 1
3143
+ A specific integer for the month of the year from which we start half-years.
3144
+
3145
+ See Also
3146
+ --------
3147
+ :class:`~pandas.tseries.offsets.DateOffset` : Standard kind of date increment.
3148
+
3149
+ Examples
3150
+ --------
3151
+ >>> from pandas.tseries.offsets import BHalfYearBegin
3152
+ >>> ts = pd.Timestamp('2020-05-24 05:01:15')
3153
+ >>> ts + BHalfYearBegin()
3154
+ Timestamp('2020-07-01 05:01:15')
3155
+ >>> ts + BHalfYearBegin(2)
3156
+ Timestamp('2021-01-01 05:01:15')
3157
+ >>> ts + BHalfYearBegin(startingMonth=2)
3158
+ Timestamp('2020-08-03 05:01:15')
3159
+ >>> ts + BHalfYearBegin(-1)
3160
+ Timestamp('2020-01-01 05:01:15')
3161
+ """
3162
+ _output_name = " BusinessHalfYearBegin"
3163
+ _default_starting_month = 1
3164
+ _from_name_starting_month = 1
3165
+ _prefix = " BHYS"
3166
+ _day_opt = " business_start"
3167
+
3168
+
3169
+ cdef class HalfYearEnd(HalfYearOffset):
3170
+ """
3171
+ DateOffset increments between half-year end dates.
3172
+
3173
+ startingMonth = 1 corresponds to dates like 1/31/2007, 7/31/2007, ...
3174
+ startingMonth = 2 corresponds to dates like 2/28/2007, 8/31/2007, ...
3175
+ startingMonth = 6 corresponds to dates like 6/30/2007, 12/31/2007, ...
3176
+
3177
+ Attributes
3178
+ ----------
3179
+ n : int, default 1
3180
+ The number of half-years represented.
3181
+ normalize : bool, default False
3182
+ Normalize start/end dates to midnight before generating date range.
3183
+ startingMonth : int, default 6
3184
+ A specific integer for the month of the year from which we start half-years.
3185
+
3186
+ See Also
3187
+ --------
3188
+ :class:`~pandas.tseries.offsets.DateOffset` : Standard kind of date increment.
3189
+
3190
+ Examples
3191
+ --------
3192
+ >>> ts = pd.Timestamp(2022, 1, 1)
3193
+ >>> ts + pd.offsets.HalfYearEnd()
3194
+ Timestamp('2022-06-30 00:00:00')
3195
+ """
3196
+ _default_starting_month = 6
3197
+ _from_name_starting_month = 12
3198
+ _prefix = " HYE"
3199
+ _day_opt = " end"
3200
+
3201
+
3202
+ cdef class HalfYearBegin(HalfYearOffset):
3203
+ """
3204
+ DateOffset increments between half-year start dates.
3205
+
3206
+ startingMonth = 1 corresponds to dates like 1/01/2007, 7/01/2007, ...
3207
+ startingMonth = 2 corresponds to dates like 2/01/2007, 8/01/2007, ...
3208
+ startingMonth = 3 corresponds to dates like 3/01/2007, 9/01/2007, ...
3209
+
3210
+ Attributes
3211
+ ----------
3212
+ n : int, default 1
3213
+ The number of half-years represented.
3214
+ normalize : bool, default False
3215
+ Normalize start/end dates to midnight before generating date range.
3216
+ startingMonth : int, default 1
3217
+ A specific integer for the month of the year from which we start half-years.
3218
+
3219
+ See Also
3220
+ --------
3221
+ :class:`~pandas.tseries.offsets.DateOffset` : Standard kind of date increment.
3222
+
3223
+ Examples
3224
+ --------
3225
+ >>> ts = pd.Timestamp(2022, 2, 1)
3226
+ >>> ts + pd.offsets.HalfYearBegin()
3227
+ Timestamp('2022-07-01 00:00:00')
3228
+ """
3229
+ _default_starting_month = 1
3230
+ _from_name_starting_month = 1
3231
+ _prefix = " HYS"
3232
+ _day_opt = " start"
3233
+
3234
+
3014
3235
# ----------------------------------------------------------------------
3015
3236
# Month-Based Offset Classes
3016
3237
@@ -4823,6 +5044,8 @@ prefix_mapping = {
4823
5044
BusinessMonthEnd, # 'BME'
4824
5045
BQuarterEnd, # 'BQE'
4825
5046
BQuarterBegin, # 'BQS'
5047
+ BHalfYearEnd, # 'BHYE'
5048
+ BHalfYearBegin, # 'BHYS'
4826
5049
BusinessHour, # 'bh'
4827
5050
CustomBusinessDay, # 'C'
4828
5051
CustomBusinessMonthEnd, # 'CBME'
@@ -4839,6 +5062,8 @@ prefix_mapping = {
4839
5062
Micro, # 'us'
4840
5063
QuarterEnd, # 'QE'
4841
5064
QuarterBegin, # 'QS'
5065
+ HalfYearEnd, # 'HYE'
5066
+ HalfYearBegin, # 'HYS'
4842
5067
Milli, # 'ms'
4843
5068
Hour, # 'h'
4844
5069
Day, # 'D'
0 commit comments