@@ -291,6 +291,7 @@ def __init__(self, left, right, name, na_op):
291
291
self .is_datetime64tz_lhs = is_datetime64tz_dtype (lvalues )
292
292
self .is_datetime_lhs = self .is_datetime64_lhs or self .is_datetime64tz_lhs
293
293
self .is_integer_lhs = left .dtype .kind in ['i' , 'u' ]
294
+ self .is_floating_lhs = left .dtype .kind == 'f'
294
295
295
296
# right
296
297
self .right = right
@@ -300,32 +301,25 @@ def __init__(self, left, right, name, na_op):
300
301
self .is_datetime_rhs = self .is_datetime64_rhs or self .is_datetime64tz_rhs
301
302
self .is_timedelta_rhs = is_timedelta64_dtype (rvalues )
302
303
self .is_integer_rhs = rvalues .dtype .kind in ('i' , 'u' )
304
+ self .is_floating_rhs = rvalues .dtype .kind == 'f'
303
305
304
306
self ._validate (lvalues , rvalues , name )
305
307
self .lvalues , self .rvalues = self ._convert_for_datetime (lvalues , rvalues )
306
308
307
309
def _validate (self , lvalues , rvalues , name ):
308
310
# timedelta and integer mul/div
309
311
310
- if (self .is_timedelta_lhs and self .is_integer_rhs ) or (
311
- self .is_integer_lhs and self .is_timedelta_rhs ):
312
+ if (self .is_timedelta_lhs and
313
+ (self .is_integer_rhs or self .is_floating_rhs )) or (
314
+ self .is_timedelta_rhs and
315
+ (self .is_integer_lhs or self .is_floating_lhs )):
312
316
313
- if name not in ('__div__' , '__truediv__' , '__mul__' ):
317
+ if name not in ('__div__' , '__truediv__' , '__mul__' , '__rmul__' ):
314
318
raise TypeError ("can only operate on a timedelta and an "
315
- "integer for division, but the operator [%s]"
316
- "was passed" % name )
319
+ "integer or a float for division and "
320
+ "multiplication, but the operator [%s] was"
321
+ "passed" % name )
317
322
318
- # 2 datetimes
319
- elif self .is_datetime_lhs and self .is_datetime_rhs :
320
-
321
- if name not in ('__sub__' ,'__rsub__' ):
322
- raise TypeError ("can only operate on a datetimes for"
323
- " subtraction, but the operator [%s] was"
324
- " passed" % name )
325
-
326
- # if tz's must be equal (same or None)
327
- if getattr (lvalues ,'tz' ,None ) != getattr (rvalues ,'tz' ,None ):
328
- raise ValueError ("Incompatbile tz's on datetime subtraction ops" )
329
323
330
324
# 2 timedeltas
331
325
elif ((self .is_timedelta_lhs and
@@ -339,6 +333,7 @@ def _validate(self, lvalues, rvalues, name):
339
333
"addition, subtraction, and division, but the"
340
334
" operator [%s] was passed" % name )
341
335
336
+
342
337
# datetime and timedelta/DateOffset
343
338
elif (self .is_datetime_lhs and
344
339
(self .is_timedelta_rhs or self .is_offset_rhs )):
@@ -349,6 +344,28 @@ def _validate(self, lvalues, rvalues, name):
349
344
" but the operator [%s] was passed" %
350
345
name )
351
346
347
+ elif (self .is_datetime_rhs and
348
+ (self .is_timedelta_lhs or self .is_offset_lhs )):
349
+ if name not in ('__add__' , '__radd__' , '__rsub__' ):
350
+ raise TypeError ("can only operate on a timedelta/DateOffset with a rhs of"
351
+ " a datetime for addition,"
352
+ " but the operator [%s] was passed" %
353
+ name )
354
+
355
+
356
+ # 2 datetimes
357
+ elif self .is_datetime_lhs and self .is_datetime_rhs :
358
+
359
+ if name not in ('__sub__' ,'__rsub__' ):
360
+ raise TypeError ("can only operate on a datetimes for"
361
+ " subtraction, but the operator [%s] was"
362
+ " passed" % name )
363
+
364
+ # if tz's must be equal (same or None)
365
+ if getattr (lvalues ,'tz' ,None ) != getattr (rvalues ,'tz' ,None ):
366
+ raise ValueError ("Incompatbile tz's on datetime subtraction ops" )
367
+
368
+
352
369
elif ((self .is_timedelta_lhs or self .is_offset_lhs )
353
370
and self .is_datetime_rhs ):
354
371
@@ -357,7 +374,7 @@ def _validate(self, lvalues, rvalues, name):
357
374
" a datetime for addition, but the operator"
358
375
" [%s] was passed" % name )
359
376
else :
360
- raise TypeError ('cannot operate on a series with out a rhs '
377
+ raise TypeError ('cannot operate on a series without a rhs '
361
378
'of a series/ndarray of type datetime64[ns] '
362
379
'or a timedelta' )
363
380
@@ -366,17 +383,25 @@ def _convert_to_array(self, values, name=None, other=None):
366
383
from pandas .tseries .timedeltas import to_timedelta
367
384
368
385
ovalues = values
386
+ supplied_dtype = None
369
387
if not is_list_like (values ):
370
388
values = np .array ([values ])
371
-
372
- inferred_type = lib .infer_dtype (values )
373
-
374
- if inferred_type in ('datetime64' , 'datetime' , 'date' , 'time' ):
389
+ # if this is a Series that contains relevant dtype info, then use this
390
+ # instead of the inferred type; this avoids coercing Series([NaT],
391
+ # dtype='datetime64[ns]') to Series([NaT], dtype='timedelta64[ns]')
392
+ elif isinstance (values , pd .Series ) and (
393
+ is_timedelta64_dtype (values ) or is_datetime64_dtype (values )):
394
+ supplied_dtype = values .dtype
395
+ inferred_type = supplied_dtype or lib .infer_dtype (values )
396
+ if (inferred_type in ('datetime64' , 'datetime' , 'date' , 'time' )
397
+ or com .is_datetimetz (inferred_type )):
375
398
# if we have a other of timedelta, but use pd.NaT here we
376
399
# we are in the wrong path
377
- if (other is not None and other .dtype == 'timedelta64[ns]' and
378
- all (isnull (v ) for v in values )):
379
- values = np .empty (values .shape , dtype = other .dtype )
400
+ if (supplied_dtype is None
401
+ and other is not None
402
+ and (other .dtype in ('timedelta64[ns]' , 'datetime64[ns]' ))
403
+ and isnull (values ).all ()):
404
+ values = np .empty (values .shape , dtype = 'timedelta64[ns]' )
380
405
values [:] = iNaT
381
406
382
407
# a datelike
@@ -401,18 +426,15 @@ def _convert_to_array(self, values, name=None, other=None):
401
426
values = values .astype ('timedelta64[ns]' )
402
427
elif isinstance (values , pd .PeriodIndex ):
403
428
values = values .to_timestamp ().to_series ()
404
- elif name not in ('__truediv__' , '__div__' , '__mul__' ):
429
+ elif name not in ('__truediv__' , '__div__' , '__mul__' , '__rmul__' ):
405
430
raise TypeError ("incompatible type for a datetime/timedelta "
406
431
"operation [{0}]" .format (name ))
407
432
elif inferred_type == 'floating' :
408
- # all nan, so ok, use the other dtype (e.g. timedelta or datetime)
409
- if isnull ( values ). all ( ):
433
+ if isnull ( values ). all () and name in ( '__add__' , '__radd__' ,
434
+ '__sub__' , '__rsub__' ):
410
435
values = np .empty (values .shape , dtype = other .dtype )
411
436
values [:] = iNaT
412
- else :
413
- raise TypeError (
414
- 'incompatible type [{0}] for a datetime/timedelta '
415
- 'operation' .format (np .array (values ).dtype ))
437
+ return values
416
438
elif self ._is_offset (values ):
417
439
return values
418
440
else :
@@ -431,7 +453,10 @@ def _convert_for_datetime(self, lvalues, rvalues):
431
453
432
454
# datetime subtraction means timedelta
433
455
if self .is_datetime_lhs and self .is_datetime_rhs :
434
- self .dtype = 'timedelta64[ns]'
456
+ if self .name in ('__sub__' , '__rsub__' ):
457
+ self .dtype = 'timedelta64[ns]'
458
+ else :
459
+ self .dtype = 'datetime64[ns]'
435
460
elif self .is_datetime64tz_lhs :
436
461
self .dtype = lvalues .dtype
437
462
elif self .is_datetime64tz_rhs :
@@ -482,7 +507,8 @@ def _offset(lvalues, rvalues):
482
507
rvalues = to_timedelta (rvalues )
483
508
484
509
lvalues = lvalues .astype (np .int64 )
485
- rvalues = rvalues .astype (np .int64 )
510
+ if not self .is_floating_rhs :
511
+ rvalues = rvalues .astype (np .int64 )
486
512
487
513
# time delta division -> unit less
488
514
# integer gets converted to timedelta in np < 1.6
@@ -580,7 +606,7 @@ def wrapper(left, right, name=name, na_op=na_op):
580
606
lvalues , rvalues = left , right
581
607
dtype = None
582
608
wrap_results = lambda x : x
583
- elif time_converted == NotImplemented :
609
+ elif time_converted is NotImplemented :
584
610
return NotImplemented
585
611
else :
586
612
left , right = time_converted .left , time_converted .right
0 commit comments