1
1
# -*- coding: utf-8 -*-
2
2
from datetime import timedelta
3
+ import operator
3
4
import warnings
4
5
5
6
import numpy as np
17
18
from pandas .util ._decorators import cache_readonly , deprecate_kwarg , Appender
18
19
19
20
from pandas .core .dtypes .common import (
20
- is_integer_dtype , is_float_dtype , is_period_dtype ,
21
- is_datetime64_dtype )
21
+ is_integer_dtype , is_float_dtype , is_period_dtype , is_timedelta64_dtype ,
22
+ is_datetime64_dtype , _TD_DTYPE )
22
23
from pandas .core .dtypes .dtypes import PeriodDtype
23
24
from pandas .core .dtypes .generic import ABCSeries
24
25
from pandas .core .dtypes .missing import isna
@@ -380,24 +381,54 @@ def _add_offset(self, other):
380
381
return self ._time_shift (other .n )
381
382
382
383
def _add_delta_td (self , other ):
384
+ assert isinstance (self .freq , Tick ) # checked by calling function
383
385
assert isinstance (other , (timedelta , np .timedelta64 , Tick ))
384
- nanos = delta_to_nanoseconds (other )
385
- own_offset = frequencies .to_offset (self .freq .rule_code )
386
386
387
- if isinstance (own_offset , Tick ):
388
- offset_nanos = delta_to_nanoseconds (own_offset )
389
- if np .all (nanos % offset_nanos == 0 ):
390
- return self ._time_shift (nanos // offset_nanos )
387
+ delta = self ._check_timedeltalike_freq_compat (other )
391
388
392
- # raise when input doesn't have freq
393
- raise IncompatibleFrequency ("Input has different freq from "
394
- "{cls}(freq={freqstr})"
395
- .format (cls = type (self ).__name__ ,
396
- freqstr = self .freqstr ))
389
+ # Note: when calling parent class's _add_delta_td, it will call
390
+ # delta_to_nanoseconds(delta). Because delta here is an integer,
391
+ # delta_to_nanoseconds will return it unchanged.
392
+ return DatetimeLikeArrayMixin ._add_delta_td (self , delta )
393
+
394
+ def _add_delta_tdi (self , other ):
395
+ assert isinstance (self .freq , Tick ) # checked by calling function
396
+
397
+ delta = self ._check_timedeltalike_freq_compat (other )
398
+ return self ._addsub_int_array (delta , operator .add )
397
399
398
400
def _add_delta (self , other ):
399
- ordinal_delta = self ._maybe_convert_timedelta (other )
400
- return self ._time_shift (ordinal_delta )
401
+ """
402
+ Add a timedelta-like, Tick, or TimedeltaIndex-like object
403
+ to self.
404
+
405
+ Parameters
406
+ ----------
407
+ other : {timedelta, np.timedelta64, Tick,
408
+ TimedeltaIndex, ndarray[timedelta64]}
409
+
410
+ Returns
411
+ -------
412
+ result : same type as self
413
+ """
414
+ if not isinstance (self .freq , Tick ):
415
+ # We cannot add timedelta-like to non-tick PeriodArray
416
+ raise IncompatibleFrequency ("Input has different freq from "
417
+ "{cls}(freq={freqstr})"
418
+ .format (cls = type (self ).__name__ ,
419
+ freqstr = self .freqstr ))
420
+
421
+ # TODO: standardize across datetimelike subclasses whether to return
422
+ # i8 view or _shallow_copy
423
+ if isinstance (other , (Tick , timedelta , np .timedelta64 )):
424
+ new_values = self ._add_delta_td (other )
425
+ return self ._shallow_copy (new_values )
426
+ elif is_timedelta64_dtype (other ):
427
+ # ndarray[timedelta64] or TimedeltaArray/index
428
+ new_values = self ._add_delta_tdi (other )
429
+ return self ._shallow_copy (new_values )
430
+ else : # pragma: no cover
431
+ raise TypeError (type (other ).__name__ )
401
432
402
433
@deprecate_kwarg (old_arg_name = 'n' , new_arg_name = 'periods' )
403
434
def shift (self , periods ):
@@ -453,14 +484,9 @@ def _maybe_convert_timedelta(self, other):
453
484
other , (timedelta , np .timedelta64 , Tick , np .ndarray )):
454
485
offset = frequencies .to_offset (self .freq .rule_code )
455
486
if isinstance (offset , Tick ):
456
- if isinstance (other , np .ndarray ):
457
- nanos = np .vectorize (delta_to_nanoseconds )(other )
458
- else :
459
- nanos = delta_to_nanoseconds (other )
460
- offset_nanos = delta_to_nanoseconds (offset )
461
- check = np .all (nanos % offset_nanos == 0 )
462
- if check :
463
- return nanos // offset_nanos
487
+ # _check_timedeltalike_freq_compat will raise if incompatible
488
+ delta = self ._check_timedeltalike_freq_compat (other )
489
+ return delta
464
490
elif isinstance (other , DateOffset ):
465
491
freqstr = other .rule_code
466
492
base = frequencies .get_base_alias (freqstr )
@@ -479,6 +505,58 @@ def _maybe_convert_timedelta(self, other):
479
505
raise IncompatibleFrequency (msg .format (cls = type (self ).__name__ ,
480
506
freqstr = self .freqstr ))
481
507
508
+ def _check_timedeltalike_freq_compat (self , other ):
509
+ """
510
+ Arithmetic operations with timedelta-like scalars or array `other`
511
+ are only valid if `other` is an integer multiple of `self.freq`.
512
+ If the operation is valid, find that integer multiple. Otherwise,
513
+ raise because the operation is invalid.
514
+
515
+ Parameters
516
+ ----------
517
+ other : timedelta, np.timedelta64, Tick,
518
+ ndarray[timedelta64], TimedeltaArray, TimedeltaIndex
519
+
520
+ Returns
521
+ -------
522
+ multiple : int or ndarray[int64]
523
+
524
+ Raises
525
+ ------
526
+ IncompatibleFrequency
527
+ """
528
+ assert isinstance (self .freq , Tick ) # checked by calling function
529
+ own_offset = frequencies .to_offset (self .freq .rule_code )
530
+ base_nanos = delta_to_nanoseconds (own_offset )
531
+
532
+ if isinstance (other , (timedelta , np .timedelta64 , Tick )):
533
+ nanos = delta_to_nanoseconds (other )
534
+
535
+ elif isinstance (other , np .ndarray ):
536
+ # numpy timedelta64 array; all entries must be compatible
537
+ assert other .dtype .kind == 'm'
538
+ if other .dtype != _TD_DTYPE :
539
+ # i.e. non-nano unit
540
+ # TODO: disallow unit-less timedelta64
541
+ other = other .astype (_TD_DTYPE )
542
+ nanos = other .view ('i8' )
543
+ else :
544
+ # TimedeltaArray/Index
545
+ nanos = other .asi8
546
+
547
+ if np .all (nanos % base_nanos == 0 ):
548
+ # nanos being added is an integer multiple of the
549
+ # base-frequency to self.freq
550
+ delta = nanos // base_nanos
551
+ # delta is the integer (or integer-array) number of periods
552
+ # by which will be added to self.
553
+ return delta
554
+
555
+ raise IncompatibleFrequency ("Input has different freq from "
556
+ "{cls}(freq={freqstr})"
557
+ .format (cls = type (self ).__name__ ,
558
+ freqstr = self .freqstr ))
559
+
482
560
483
561
PeriodArrayMixin ._add_comparison_ops ()
484
562
PeriodArrayMixin ._add_datetimelike_methods ()
0 commit comments