Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Override operator + to make date + time = datetime in Python

I have a couple classes extending builtin datetime.*

Is there any good reason to not overload + (MyTime.__radd___) so MyDate + MyTime returns a MyDateTime?

like image 928
Paulo Scardine Avatar asked Nov 08 '10 20:11

Paulo Scardine


5 Answers

This is already implemented as a class method, datetime.datetime.combine:

import datetime
d = datetime.date(2010, 12, 5)
t = datetime.time(10, 22, 15)
dt = datetime.datetime.combine(d, t)
print dt

prints

2010-12-05 10:22:15
like image 186
nosklo Avatar answered Nov 19 '22 23:11

nosklo


This would generally be frowned upon because you're really combining rather than adding; this is why the actual datetime library has a combine method rather than using addition in this way.

I'm not aware of any other cases in Python where <instance of TypeA> + <instance of TypeB> produces <instance of TypeC>. Thus, the Principle of least astonishment suggests that you should simply provide a combine method rather than overload addition.

like image 23
Eli Courtwright Avatar answered Nov 20 '22 00:11

Eli Courtwright


Yes, there is at least one good reason not to: the resulting instance is completely different from the two input instances. Is this important? I don't think so -- consider that date - date yields timedelta.

The way I see it:

  • Does adding two dates together make sense? No.
  • Does adding two times together make sense? No.
  • Does adding a date and a time together make sense? Yup!
  • Does adding a date and a timedelta togethor make sense? Maybe.
  • Does adding a time and a timedelta together make sense? Maybe.

and for subtraction

  • Does subtracting two dates make sense? Yes.
  • Does subtracting two times make sense? Yes.
  • Does subtracting a time from a date make sense? Nope.
  • Does subtracting a timedelta from a date make sense? Maybe.
  • Does subtracting a timedelta from a time make sense? Maybe.

Developing along the lines of what makes sense:

date + time      => datetime
date + timedelta => date | datetime or exception or silently drop time portion

time + date => datetime
time + timedelta => time | wrap-around or exception

date - date      => timedelta
date - timedelta => date | datetime or exception or silently drop time portion

time - time      => timedelta
time - timedelta => time | wrap-around or exception

datetime + timedelta => datetime
datetime - timedelta => datetime

So, if it were me and I were designing a Date, Time, DateTime, TimeDelta framework, I would allow:

date + time
date - date
time - time
datetime + timedelta
datetime - timedelta

and for these:

date +/- timedelta
time +/- timedelta

I would default to returning the same type if the timedelta had none of the other type, and raising an exception if the timedelta did have some of the other type, but there would be a setting that would control that. The other possible behavior would be to drop the unneeded portion -- so a date combined with a timedelta that had hours would drop the hours and return a date.

like image 23
Ethan Furman Avatar answered Nov 20 '22 00:11

Ethan Furman


Due to the existence of the date, time, and datetime cross-type addition and subtraction operators, I would think that this is fine, so long as it is well defined.

Currently (2.7.2):

date = date + timedelta
date = date - timedelta
timedelta = date - date

datetime = datetime + timedelta
datetime = datetime - timedelta
timedelta = datetime - datetime

I believe the following is also reasonable for an extension:

timedelta = time - time
datetime = date + time

I was going to suggest the following as well, but time has very specific min and max values for hour, minute, second, and microsecond, thus requiring a silent wraparound of values or returning of a different type:

time = time + timedelta
time = time - timedelta

Similarly, date cannot handle a timedelta of less than a day being added to it. Often I have been told to simply use Duck Typing with Python, because that's the intent. If that is true, then I would propose the following completed interface:

[date|datetime] = date + timedelta
[date|datetime] = date - timedelta
timedelta = date - date

[time|timedelta] = time + timedelta
[time|timedelta] = time - timedelta
timedelta = time - time

datetime = datetime + timedelta
datetime = datetime - timedelta
datetime = date + time
datetime = date - time
timedelta = datetime - datetime
timedelta = datetime - date

timedelta = timedelta + timedelta
timedelta = timedelta - timedelta

In which, given the case that date has precision loss (for timedelta's with partial days), it is promoted to datetime. Similarly, given the case that time has precision loss (for timedelta's that yield a result of more than one day, or negative time), it is promoted to timedelta. However, I'm not fully comfortable with [time|timedelta]. It makes sense given the rest of the interface from parallelism and precision views, but I do think it might be more elegant to just wraparound the time to the proper hour, thus changing all the [time|timedelta]'s to simply time, but unfortunately that leaves us with lost precision.

like image 1
Mark Ribau Avatar answered Nov 19 '22 23:11

Mark Ribau


In my opinion, the most valuable uses of operator overloading are situations where many input values can be combined. You'd never want to deal with:

concat(concat(concat("Hello", ", "), concat("World", "!")), '\n');

or

distance = sqrt(add(add(x*x, y*y), z*z));

So we overload math symbols to create a more intuitive syntax. Another way to deal with this problem is variadic functions, like + in Scheme.

With your date + time = datetime, it doesn't make sense to add datetime + datetime, datetime + time, or datetime + date, so you could never encounter a situation like those above.

In my opinion, once again, the right thing is to use a constructor method. In a language with strong typing like C++, you'd have DateTime(const Date &d, const Time &t). With Python's dynamic typing, I guess they gave the function a name, datetime.combine(date, time), to make the code clearer when the types of the input variables are not visible in the code.

like image 1
japreiss Avatar answered Nov 20 '22 01:11

japreiss