2
2
# cython: profile=False
3
3
4
4
cimport cython
5
+ from cython cimport Py_ssize_t
5
6
6
7
import time
7
8
from cpython.datetime cimport datetime, timedelta, time as dt_time
@@ -10,6 +11,7 @@ from dateutil.relativedelta import relativedelta
10
11
11
12
import numpy as np
12
13
cimport numpy as np
14
+ from numpy cimport int64_t
13
15
np.import_array()
14
16
15
17
@@ -19,6 +21,10 @@ from pandas._libs.tslib import monthrange
19
21
20
22
from conversion cimport tz_convert_single, pydt_to_i8
21
23
from frequencies cimport get_freq_code
24
+ from nattype cimport NPY_NAT
25
+ from np_datetime cimport (pandas_datetimestruct,
26
+ dtstruct_to_dt64, dt64_to_dtstruct,
27
+ is_leapyear, days_per_month_table)
22
28
23
29
# ---------------------------------------------------------------------
24
30
# Constants
@@ -419,13 +425,121 @@ class BaseOffset(_BaseOffset):
419
425
# ----------------------------------------------------------------------
420
426
# RelativeDelta Arithmetic
421
427
428
+ @ cython.wraparound (False )
429
+ @ cython.boundscheck (False )
430
+ cdef inline int get_days_in_month(int year, int month) nogil:
431
+ return days_per_month_table[is_leapyear(year)][month - 1 ]
432
+
433
+
434
+ cdef inline int year_add_months(pandas_datetimestruct dts, int months) nogil:
435
+ """ new year number after shifting pandas_datetimestruct number of months"""
436
+ return dts.year + (dts.month + months - 1 ) / 12
437
+
438
+
439
+ cdef inline int month_add_months(pandas_datetimestruct dts, int months) nogil:
440
+ """
441
+ New month number after shifting pandas_datetimestruct
442
+ number of months.
443
+ """
444
+ cdef int new_month = (dts.month + months) % 12
445
+ return 12 if new_month == 0 else new_month
446
+
447
+
448
+ @ cython.wraparound (False )
449
+ @ cython.boundscheck (False )
450
+ def shift_months (int64_t[:] dtindex , int months , object day = None ):
451
+ """
452
+ Given an int64-based datetime index, shift all elements
453
+ specified number of months using DateOffset semantics
454
+
455
+ day: {None, 'start', 'end'}
456
+ * None: day of month
457
+ * 'start' 1st day of month
458
+ * 'end' last day of month
459
+ """
460
+ cdef:
461
+ Py_ssize_t i
462
+ pandas_datetimestruct dts
463
+ int count = len (dtindex)
464
+ int months_to_roll
465
+ bint roll_check
466
+ int64_t[:] out = np.empty(count, dtype = ' int64' )
467
+
468
+ if day is None :
469
+ with nogil:
470
+ for i in range (count):
471
+ if dtindex[i] == NPY_NAT:
472
+ out[i] = NPY_NAT
473
+ continue
474
+
475
+ dt64_to_dtstruct(dtindex[i], & dts)
476
+ dts.year = year_add_months(dts, months)
477
+ dts.month = month_add_months(dts, months)
478
+
479
+ dts.day = min (dts.day, get_days_in_month(dts.year, dts.month))
480
+ out[i] = dtstruct_to_dt64(& dts)
481
+ elif day == ' start' :
482
+ roll_check = False
483
+ if months <= 0 :
484
+ months += 1
485
+ roll_check = True
486
+ with nogil:
487
+ for i in range (count):
488
+ if dtindex[i] == NPY_NAT:
489
+ out[i] = NPY_NAT
490
+ continue
491
+
492
+ dt64_to_dtstruct(dtindex[i], & dts)
493
+ months_to_roll = months
494
+
495
+ # offset semantics - if on the anchor point and going backwards
496
+ # shift to next
497
+ if roll_check and dts.day == 1 :
498
+ months_to_roll -= 1
499
+
500
+ dts.year = year_add_months(dts, months_to_roll)
501
+ dts.month = month_add_months(dts, months_to_roll)
502
+ dts.day = 1
503
+
504
+ out[i] = dtstruct_to_dt64(& dts)
505
+ elif day == ' end' :
506
+ roll_check = False
507
+ if months > 0 :
508
+ months -= 1
509
+ roll_check = True
510
+ with nogil:
511
+ for i in range (count):
512
+ if dtindex[i] == NPY_NAT:
513
+ out[i] = NPY_NAT
514
+ continue
515
+
516
+ dt64_to_dtstruct(dtindex[i], & dts)
517
+ months_to_roll = months
518
+
519
+ # similar semantics - when adding shift forward by one
520
+ # month if already at an end of month
521
+ if roll_check and dts.day == get_days_in_month(dts.year,
522
+ dts.month):
523
+ months_to_roll += 1
524
+
525
+ dts.year = year_add_months(dts, months_to_roll)
526
+ dts.month = month_add_months(dts, months_to_roll)
527
+
528
+ dts.day = get_days_in_month(dts.year, dts.month)
529
+ out[i] = dtstruct_to_dt64(& dts)
530
+ else :
531
+ raise ValueError (" day must be None, 'start' or 'end'" )
532
+
533
+ return np.asarray(out)
534
+
535
+
422
536
cpdef datetime shift_month(datetime stamp, int months, object day_opt = None ):
423
537
"""
424
538
Given a datetime (or Timestamp) `stamp`, an integer `months` and an
425
539
option `day_opt`, return a new datetimelike that many months later,
426
540
with day determined by `day_opt` using relativedelta semantics.
427
541
428
- Scalar analogue of tslib. shift_months
542
+ Scalar analogue of shift_months
429
543
430
544
Parameters
431
545
----------
0 commit comments