In Python, in an operation of numbers of mixed type, the narrower type is widened to that of the other, such as int
+ float
→ float
:
In [57]: 3 + 0.1
Out[57]: 3.1
But for datetime.date
, we have datetime.date
+ datetime.timedelta
→ datetime.date
, not datetime.datetime
:
In [58]: datetime.date(2013, 1, 1) + datetime.timedelta(seconds=42)
Out[58]: datetime.date(2013, 1, 1)
Why is the widening reasoning applied to numbers, but not to date
/datetime
/timedelta
?
(Background: I'm writing a reading routine for a file format where one field is year, one field is day-of-year, one field is milliseconds-since-midnight. Of course, the simple and explicit solution is datetime.datetime(2013, 1, 1, 0, 0, 0) + datetime.timedelta(seconds=42)
, but one could equally reason that one should rewrite 3 + 0.1
as 3.0 + 0.1
)
timedelta Objects. A timedelta object represents a duration, the difference between two dates or times. class datetime. timedelta (days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)
Timedeltas are differences in times, expressed in difference units, e.g. days, hours, minutes, seconds. They can be both positive and negative. Timedelta is a subclass of datetime.
I assume you want to extract the time component as a string, removing all reference to "days" even if your Timedelta is greater than 1 day. One way is to convert your Timedelta to a normalised datetime and then use the time attribute. Below is also a trivial way to convert your Timedelta objects to strings.
timedelta() function. Python timedelta() function is present under datetime library which is generally used for calculating differences in dates and also can be used for date manipulations in Python. It is one of the easiest ways to perform date manipulations.
The timedelta
object doesn't store any information on whether or not it just concerns dates, or also times. (The fact that the number of hours/minutes/seconds/micros is 0 may just be a coincidence!)
Hence, suppose we have someone who just wants to manipulate dates, ignoring times, she'd do something like my_new_date = my_old_date + timedelta(days=1)
. She'd be very surprised and possibly annoyed to find that my_new_date
is now a datetime
object rather than a date
object.
The behaviour is documented:
date2 is moved forward in time if
timedelta.days > 0
, or backward iftimedelta.days < 0
. Afterwarddate2 - date1 == timedelta.days
.timedelta.seconds
andtimedelta.microseconds
are ignored.
(My emphasis. This behaviour has remained unchanged since date
objects were added in Python 2.3.)
I haven't been able to find any evidence as to why the module is designed like this. Certainly there are use cases like yours where you want to represent the point in time corresponding to the midnight at the start of a day. In these cases it is annoying to have to convert back and forth. But there are other use cases in which you want to represent a whole day (and not just some point in time on that day), in which case you don't want to accidentally end up with partial days when you add timedeltas.
Chris Withers suggested that the behaviour be changed, in issue 3249, but Tim Peters noted that:
an incompatible change to documented always-worked-this-way behavior is unlikely to be accepted.
If you want an object that behaves like a datetime.date
, but where arithmetic operations return datetime.datetime
objects, then it shouldn't be not too hard to write one:
from datetime import date, datetime, time, timedelta
def _part_day(t):
"""Return True if t is a timedelta object that does not consist of
whole days.
"""
return isinstance(t, timedelta) and (t.seconds or t.microseconds)
class mydate(date):
"""Subclass of datetime.date where arithmetic operations with a
timedelta object return a datetime.datetime object unless the
timedelta object consists of whole days.
"""
def datetime(self):
"""Return datetime corresponding to the midnight at start of this
date.
"""
return datetime.combine(self, time())
def __add__(self, other):
if _part_day(other):
return self.datetime() + other
else:
return super().__add__(other)
__radd__ = __add__
def __sub__(self, other):
if _part_day(other):
return self.datetime() - other
else:
return super().__sub__(other)
(This is untested, but it's shouldn't be hard to get it working from here.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With