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