@@ -3847,6 +3847,7 @@ def get_time_micros(ndarray[int64_t] dtindex):
3847
3847
3848
3848
return micros
3849
3849
3850
+
3850
3851
@ cython.wraparound (False )
3851
3852
def get_date_field (ndarray[int64_t] dtindex , object field ):
3852
3853
'''
@@ -4386,6 +4387,75 @@ cpdef normalize_date(object dt):
4386
4387
raise TypeError (' Unrecognized type: %s ' % type (dt))
4387
4388
4388
4389
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
+
4389
4459
# ----------------------------------------------------------------------
4390
4460
# Don't even ask
4391
4461
0 commit comments