@@ -3011,6 +3011,228 @@ cdef class QuarterBegin(QuarterOffset):
30113011 _day_opt = " start"
30123012
30133013
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+
30143236# ----------------------------------------------------------------------
30153237# Month-Based Offset Classes
30163238
@@ -4823,6 +5045,8 @@ prefix_mapping = {
48235045 BusinessMonthEnd, # 'BME'
48245046 BQuarterEnd, # 'BQE'
48255047 BQuarterBegin, # 'BQS'
5048+ BHalfYearEnd, # 'BHYE'
5049+ BHalfYearBegin, # 'BHYS'
48265050 BusinessHour, # 'bh'
48275051 CustomBusinessDay, # 'C'
48285052 CustomBusinessMonthEnd, # 'CBME'
@@ -4839,6 +5063,8 @@ prefix_mapping = {
48395063 Micro, # 'us'
48405064 QuarterEnd, # 'QE'
48415065 QuarterBegin, # 'QS'
5066+ HalfYearEnd, # 'HYE'
5067+ HalfYearBegin, # 'HYS'
48425068 Milli, # 'ms'
48435069 Hour, # 'h'
48445070 Day, # 'D'
0 commit comments