@@ -3847,6 +3847,7 @@ def get_time_micros(ndarray[int64_t] dtindex):
38473847
38483848 return micros
38493849
3850+
38503851@ cython.wraparound (False )
38513852def get_date_field (ndarray[int64_t] dtindex , object field ):
38523853 '''
@@ -4386,6 +4387,75 @@ cpdef normalize_date(object dt):
43864387 raise TypeError (' Unrecognized type: %s ' % type (dt))
43874388
43884389
4390+ cdef inline int _year_add_months(pandas_datetimestruct dts,
4391+ int months):
4392+ ''' new year number after shifting pandas_datetimestruct number of months'''
4393+ return dts.year + (dts.month + months - 1 ) / 12
4394+
4395+ cdef inline int _month_add_months(pandas_datetimestruct dts,
4396+ int months):
4397+ ''' new month number after shifting pandas_datetimestruct number of months'''
4398+ cdef int new_month = (dts.month + months) % 12
4399+ return 12 if new_month == 0 else new_month
4400+
4401+ @ cython.wraparound (False )
4402+ def shift_months (int64_t[:] dtindex , int months , object day = None ):
4403+ '''
4404+ Given an int64-based datetime index, shift all elements
4405+ specified number of months using DateOffset semantics
4406+
4407+ day: {None, 'start', 'end'}
4408+ * None: day of month
4409+ * 'start' 1st day of month
4410+ * 'end' last day of month
4411+ '''
4412+ cdef:
4413+ Py_ssize_t i
4414+ int days_in_month
4415+ pandas_datetimestruct dts
4416+ int count = len (dtindex)
4417+ int64_t[:] out = np.empty(count, dtype = ' int64' )
4418+
4419+ for i in range (count):
4420+ if dtindex[i] == NPY_NAT:
4421+ out[i] = NPY_NAT
4422+ else :
4423+ pandas_datetime_to_datetimestruct(dtindex[i], PANDAS_FR_ns, & dts)
4424+
4425+ if day is None :
4426+ dts.year = _year_add_months(dts, months)
4427+ dts.month = _month_add_months(dts, months)
4428+ # prevent day from wrapping around month end
4429+ days_in_month = days_per_month_table[is_leapyear(dts.year)][dts.month- 1 ]
4430+ dts.day = min (dts.day, days_in_month)
4431+ elif day == ' start' :
4432+ dts.year = _year_add_months(dts, months)
4433+ dts.month = _month_add_months(dts, months)
4434+
4435+ # offset semantics - when subtracting if at the start anchor
4436+ # point, shift back by one more month
4437+ if months <= 0 and dts.day == 1 :
4438+ dts.year = _year_add_months(dts, - 1 )
4439+ dts.month = _month_add_months(dts, - 1 )
4440+ else :
4441+ dts.day = 1
4442+ elif day == ' end' :
4443+ days_in_month = days_per_month_table[is_leapyear(dts.year)][dts.month- 1 ]
4444+ dts.year = _year_add_months(dts, months)
4445+ dts.month = _month_add_months(dts, months)
4446+
4447+ # similar semantics - when adding shift forward by one
4448+ # month if already at an end of month
4449+ if months >= 0 and dts.day == days_in_month:
4450+ dts.year = _year_add_months(dts, 1 )
4451+ dts.month = _month_add_months(dts, 1 )
4452+
4453+ days_in_month = days_per_month_table[is_leapyear(dts.year)][dts.month- 1 ]
4454+ dts.day = days_in_month
4455+
4456+ out[i] = pandas_datetimestruct_to_datetime(PANDAS_FR_ns, & dts)
4457+ return np.asarray(out)
4458+
43894459# ----------------------------------------------------------------------
43904460# Don't even ask
43914461
0 commit comments