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 )
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
@@ -362,24 +363,54 @@ def _add_offset(self, other):
362
363
return self ._time_shift (other .n )
363
364
364
365
def _add_delta_td (self , other ):
366
+ assert isinstance (self .freq , Tick ) # checked by calling function
365
367
assert isinstance (other , (timedelta , np .timedelta64 , Tick ))
366
- nanos = delta_to_nanoseconds (other )
367
- own_offset = frequencies .to_offset (self .freq .rule_code )
368
368
369
- if isinstance (own_offset , Tick ):
370
- offset_nanos = delta_to_nanoseconds (own_offset )
371
- if np .all (nanos % offset_nanos == 0 ):
372
- return self ._time_shift (nanos // offset_nanos )
369
+ delta = self ._check_timedeltalike_freq_compat (other )
373
370
374
- # raise when input doesn't have freq
375
- raise IncompatibleFrequency ("Input has different freq from "
376
- "{cls}(freq={freqstr})"
377
- .format (cls = type (self ).__name__ ,
378
- freqstr = self .freqstr ))
371
+ # Note: when calling parent class's _add_delta_td, it will call
372
+ # delta_to_nanoseconds(delta). Because delta here is an integer,
373
+ # delta_to_nanoseconds will return it unchanged.
374
+ return DatetimeLikeArrayMixin ._add_delta_td (self , delta )
375
+
376
+ def _add_delta_tdi (self , other ):
377
+ assert isinstance (self .freq , Tick ) # checked by calling function
378
+
379
+ delta = self ._check_timedeltalike_freq_compat (other )
380
+ return self ._addsub_int_array (delta , operator .add )
379
381
380
382
def _add_delta (self , other ):
381
- ordinal_delta = self ._maybe_convert_timedelta (other )
382
- return self ._time_shift (ordinal_delta )
383
+ """
384
+ Add a timedelta-like, Tick, or TimedeltaIndex-like object
385
+ to self.
386
+
387
+ Parameters
388
+ ----------
389
+ other : {timedelta, np.timedelta64, Tick,
390
+ TimedeltaIndex, ndarray[timedelta64]}
391
+
392
+ Returns
393
+ -------
394
+ result : same type as self
395
+ """
396
+ if not isinstance (self .freq , Tick ):
397
+ # We cannot add timedelta-like to non-tick PeriodArray
398
+ raise IncompatibleFrequency ("Input has different freq from "
399
+ "{cls}(freq={freqstr})"
400
+ .format (cls = type (self ).__name__ ,
401
+ freqstr = self .freqstr ))
402
+
403
+ # TODO: standardize across datetimelike subclasses whether to return
404
+ # i8 view or _shallow_copy
405
+ if isinstance (other , (Tick , timedelta , np .timedelta64 )):
406
+ new_values = self ._add_delta_td (other )
407
+ return self ._shallow_copy (new_values )
408
+ elif is_timedelta64_dtype (other ):
409
+ # ndarray[timedelta64] or TimedeltaArray/index
410
+ new_values = self ._add_delta_tdi (other )
411
+ return self ._shallow_copy (new_values )
412
+ else : # pragma: no cover
413
+ raise TypeError (type (other ).__name__ )
383
414
384
415
@deprecate_kwarg (old_arg_name = 'n' , new_arg_name = 'periods' )
385
416
def shift (self , periods ):
@@ -435,14 +466,9 @@ def _maybe_convert_timedelta(self, other):
435
466
other , (timedelta , np .timedelta64 , Tick , np .ndarray )):
436
467
offset = frequencies .to_offset (self .freq .rule_code )
437
468
if isinstance (offset , Tick ):
438
- if isinstance (other , np .ndarray ):
439
- nanos = np .vectorize (delta_to_nanoseconds )(other )
440
- else :
441
- nanos = delta_to_nanoseconds (other )
442
- offset_nanos = delta_to_nanoseconds (offset )
443
- check = np .all (nanos % offset_nanos == 0 )
444
- if check :
445
- return nanos // offset_nanos
469
+ # _check_timedeltalike_freq_compat will raise if incompatible
470
+ delta = self ._check_timedeltalike_freq_compat (other )
471
+ return delta
446
472
elif isinstance (other , DateOffset ):
447
473
freqstr = other .rule_code
448
474
base = frequencies .get_base_alias (freqstr )
@@ -461,6 +487,58 @@ def _maybe_convert_timedelta(self, other):
461
487
raise IncompatibleFrequency (msg .format (cls = type (self ).__name__ ,
462
488
freqstr = self .freqstr ))
463
489
490
+ def _check_timedeltalike_freq_compat (self , other ):
491
+ """
492
+ Arithmetic operations with timedelta-like scalars or array `other`
493
+ are only valid if `other` is an integer multiple of `self.freq`.
494
+ If the operation is valid, find that integer multiple. Otherwise,
495
+ raise because the operation is invalid.
496
+
497
+ Parameters
498
+ ----------
499
+ other : timedelta, np.timedelta64, Tick,
500
+ ndarray[timedelta64], TimedeltaArray, TimedeltaIndex
501
+
502
+ Returns
503
+ -------
504
+ multiple : int or ndarray[int64]
505
+
506
+ Raises
507
+ ------
508
+ IncompatibleFrequency
509
+ """
510
+ assert isinstance (self .freq , Tick ) # checked by calling function
511
+ own_offset = frequencies .to_offset (self .freq .rule_code )
512
+ base_nanos = delta_to_nanoseconds (own_offset )
513
+
514
+ if isinstance (other , (timedelta , np .timedelta64 , Tick )):
515
+ nanos = delta_to_nanoseconds (other )
516
+
517
+ elif isinstance (other , np .ndarray ):
518
+ # numpy timedelta64 array; all entries must be compatible
519
+ assert other .dtype .kind == 'm'
520
+ if other .dtype != _TD_DTYPE :
521
+ # i.e. non-nano unit
522
+ # TODO: disallow unit-less timedelta64
523
+ other = other .astype (_TD_DTYPE )
524
+ nanos = other .view ('i8' )
525
+ else :
526
+ # TimedeltaArray/Index
527
+ nanos = other .asi8
528
+
529
+ if np .all (nanos % base_nanos == 0 ):
530
+ # nanos being added is an integer multiple of the
531
+ # base-frequency to self.freq
532
+ delta = nanos // base_nanos
533
+ # delta is the integer (or integer-array) number of periods
534
+ # by which will be added to self.
535
+ return delta
536
+
537
+ raise IncompatibleFrequency ("Input has different freq from "
538
+ "{cls}(freq={freqstr})"
539
+ .format (cls = type (self ).__name__ ,
540
+ freqstr = self .freqstr ))
541
+
464
542
465
543
PeriodArrayMixin ._add_comparison_ops ()
466
544
PeriodArrayMixin ._add_datetimelike_methods ()
0 commit comments