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