@@ -2269,56 +2269,62 @@ cdef class SemiMonthOffset(SingleConstructorOffset):
2269
2269
raise NotImplementedError (self )
2270
2270
2271
2271
@apply_index_wraps
2272
+ @ cython.wraparound (False )
2273
+ @ cython.boundscheck (False )
2272
2274
def apply_index (self , dtindex ):
2273
- # determine how many days away from the 1st of the month we are
2274
-
2275
- dti = dtindex
2276
- i8other = dtindex.asi8
2277
- days_from_start = dtindex.to_perioddelta(" M" ).asi8
2278
- delta = Timedelta(days = self .day_of_month - 1 ).value
2279
-
2280
- # get boolean array for each element before the day_of_month
2281
- before_day_of_month = days_from_start < delta
2282
-
2283
- # get boolean array for each element after the day_of_month
2284
- after_day_of_month = days_from_start > delta
2285
-
2286
- # determine the correct n for each date in dtindex
2287
- roll = self ._get_roll(i8other, before_day_of_month, after_day_of_month)
2288
-
2289
- # isolate the time since it will be striped away one the next line
2290
- time = (i8other % DAY_NANOS).view(" timedelta64[ns]" )
2291
-
2292
- # apply the correct number of months
2293
-
2294
- # integer-array addition on PeriodIndex is deprecated,
2295
- # so we use _addsub_int_array directly
2296
- asper = dtindex.to_period(" M" )
2275
+ cdef:
2276
+ int64_t[:] i8other = dtindex.view(" i8" )
2277
+ Py_ssize_t i, count = len (i8other)
2278
+ int64_t val
2279
+ int64_t[:] out = np.empty(count, dtype = " i8" )
2280
+ npy_datetimestruct dts
2281
+ int months, to_day, nadj, n = self .n
2282
+ int days_in_month, day, anchor_dom = self .day_of_month
2283
+ bint is_start = isinstance (self , SemiMonthBegin)
2297
2284
2298
- shifted = asper._addsub_int_array(roll // 2 , operator.add)
2299
- dtindex = type (dti)(shifted.to_timestamp())
2300
- dt64other = np.asarray(dtindex)
2285
+ with nogil:
2286
+ for i in range (count):
2287
+ val = i8other[i]
2288
+ if val == NPY_NAT:
2289
+ out[i] = NPY_NAT
2290
+ continue
2301
2291
2302
- # apply the correct day
2303
- dt64result = self ._apply_index_days(dt64other, roll)
2292
+ dt64_to_dtstruct(val, & dts)
2293
+ day = dts.day
2294
+
2295
+ # Adjust so that we are always looking at self.day_of_month,
2296
+ # incrementing/decrementing n if necessary.
2297
+ nadj = roll_convention(day, n, anchor_dom)
2298
+
2299
+ days_in_month = get_days_in_month(dts.year, dts.month)
2300
+ # For SemiMonthBegin on other.day == 1 and
2301
+ # SemiMonthEnd on other.day == days_in_month,
2302
+ # shifting `other` to `self.day_of_month` _always_ requires
2303
+ # incrementing/decrementing `n`, regardless of whether it is
2304
+ # initially positive.
2305
+ if is_start and (n <= 0 and day == 1 ):
2306
+ nadj -= 1
2307
+ elif (not is_start) and (n > 0 and day == days_in_month):
2308
+ nadj += 1
2309
+
2310
+ if is_start:
2311
+ # See also: SemiMonthBegin._apply
2312
+ months = nadj // 2 + nadj % 2
2313
+ to_day = 1 if nadj % 2 else anchor_dom
2304
2314
2305
- return dt64result + time
2315
+ else :
2316
+ # See also: SemiMonthEnd._apply
2317
+ months = nadj // 2
2318
+ to_day = 31 if nadj % 2 else anchor_dom
2306
2319
2307
- def _get_roll (self , i8other , before_day_of_month , after_day_of_month ):
2308
- """
2309
- Return an array with the correct n for each date in dtindex.
2320
+ dts.year = year_add_months(dts, months)
2321
+ dts.month = month_add_months(dts, months)
2322
+ days_in_month = get_days_in_month(dts.year, dts.month)
2323
+ dts.day = min (to_day, days_in_month)
2310
2324
2311
- The roll array is based on the fact that dtindex gets rolled back to
2312
- the first day of the month.
2313
- """
2314
- # before_day_of_month and after_day_of_month are ndarray[bool]
2315
- raise NotImplementedError
2325
+ out[i] = dtstruct_to_dt64(& dts)
2316
2326
2317
- def _apply_index_days (self , dt64other , roll ):
2318
- """
2319
- Apply the correct day for each date in dt64other.
2320
- """
2321
- raise NotImplementedError
2327
+ return out.base
2322
2328
2323
2329
2324
2330
cdef class SemiMonthEnd(SemiMonthOffset):
@@ -2347,39 +2353,6 @@ cdef class SemiMonthEnd(SemiMonthOffset):
2347
2353
day = 31 if n % 2 else self .day_of_month
2348
2354
return shift_month(other, months, day)
2349
2355
2350
- def _get_roll (self , i8other , before_day_of_month , after_day_of_month ):
2351
- # before_day_of_month and after_day_of_month are ndarray[bool]
2352
- n = self .n
2353
- is_month_end = get_start_end_field(i8other, " is_month_end" )
2354
- if n > 0 :
2355
- roll_end = np.where(is_month_end, 1 , 0 )
2356
- roll_before = np.where(before_day_of_month, n, n + 1 )
2357
- roll = roll_end + roll_before
2358
- elif n == 0 :
2359
- roll_after = np.where(after_day_of_month, 2 , 0 )
2360
- roll_before = np.where(~ after_day_of_month, 1 , 0 )
2361
- roll = roll_before + roll_after
2362
- else :
2363
- roll = np.where(after_day_of_month, n + 2 , n + 1 )
2364
- return roll
2365
-
2366
- def _apply_index_days (self , dt64other , roll ):
2367
- """
2368
- Add days portion of offset to dt64other.
2369
-
2370
- Parameters
2371
- ----------
2372
- dt64other : ndarray[datetime64[ns]]
2373
- roll : ndarray[int64_t]
2374
-
2375
- Returns
2376
- -------
2377
- ndarray[datetime64[ns]]
2378
- """
2379
- nanos = (roll % 2 ) * Timedelta(days = self .day_of_month).value
2380
- dt64other += nanos.astype(" timedelta64[ns]" )
2381
- return dt64other + Timedelta(days = - 1 )
2382
-
2383
2356
2384
2357
cdef class SemiMonthBegin(SemiMonthOffset):
2385
2358
"""
@@ -2405,38 +2378,6 @@ cdef class SemiMonthBegin(SemiMonthOffset):
2405
2378
day = 1 if n % 2 else self .day_of_month
2406
2379
return shift_month(other, months, day)
2407
2380
2408
- def _get_roll (self , i8other , before_day_of_month , after_day_of_month ):
2409
- # before_day_of_month and after_day_of_month are ndarray[bool]
2410
- n = self .n
2411
- is_month_start = get_start_end_field(i8other, " is_month_start" )
2412
- if n > 0 :
2413
- roll = np.where(before_day_of_month, n, n + 1 )
2414
- elif n == 0 :
2415
- roll_start = np.where(is_month_start, 0 , 1 )
2416
- roll_after = np.where(after_day_of_month, 1 , 0 )
2417
- roll = roll_start + roll_after
2418
- else :
2419
- roll_after = np.where(after_day_of_month, n + 2 , n + 1 )
2420
- roll_start = np.where(is_month_start, - 1 , 0 )
2421
- roll = roll_after + roll_start
2422
- return roll
2423
-
2424
- def _apply_index_days (self , dt64other , roll ):
2425
- """
2426
- Add days portion of offset to dt64other.
2427
-
2428
- Parameters
2429
- ----------
2430
- dt64other : ndarray[datetime64[ns]]
2431
- roll : ndarray[int64_t]
2432
-
2433
- Returns
2434
- -------
2435
- ndarray[datetime64[ns]]
2436
- """
2437
- nanos = (roll % 2 ) * Timedelta(days = self .day_of_month - 1 ).value
2438
- return dt64other + nanos.astype(" timedelta64[ns]" )
2439
-
2440
2381
2441
2382
# ---------------------------------------------------------------------
2442
2383
# Week-Based Offset Classes
0 commit comments