classification
Title: datetime: define division timedelta/timedelta
Type: feature request Stage:
Components: Library (Lib) Versions: Python 3.1, Python 2.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: tleeuwenburg@gmail.com Nosy List: amaury.forgeotdarc, belopolsky, fredrikj, haypo, jess.austin, jribbens, mark.dickinson, tleeuwenburg@gmail.com, webograph (9)
Priority: Keywords patch

Created on 2008-04-27 21:04 by webograph, last changed 2009-04-08 11:43 by tleeuwenburg@gmail.com.

Files
File name Uploaded Description Edit Remove
datetime_datetime_division.patch webograph, 2008-04-27 21:04 patch as described
datetime_datetime_division_dupcode.patch webograph, 2008-07-19 13:00 patch without function pointers
timedeltadiv.parch belopolsky, 2008-11-14 17:43 Patch with tests against revision 67223
timedelta_true_divide_divmod.patch haypo, 2008-11-14 17:51
Messages (24)
msg65902 - (view) Author: (webograph) Date: 2008-04-27 21:03
i suggest that division be defined for timedelta1/timedelta2, in that
sense that it gives how many times timedelta2 fits in timedelta1 (ie the
usual meaning of division), using integer arithmetics for floor division
(//) and returning float for truediv (/ after `from __future__ import
division`)

use case
--------

aside from the obvious how-many-times-does-a-fit-into-b, this solves the
issue of having individual methods for conversion to a number of
seconds, hours, days or nanocenturies (as described in #1673409).
example:

from datetime import timedelta
duration = timedelta(hours=1.5, seconds=20)
print "Until the time is up, you can listen to 'We will rock you' %d
times."%(duration//timedelta(minutes=5, seconds=3))
import time
time.sleep(duration/timedelta(seconds=1))


history
-------

this issue follows a discussion on python-list, re-initiated by [1].

there have previously been similar feature requests on datetime, most of
which have been rejected due to ambiguities (e.g. [2]), conflicts with
time_t or issues with time zones.

the only issue i've seen that can be relevant here is the
integer-vs-float discussion, which is here handled by floordiv (//) and
truediv.

patch
-----

i've written a patch against svn trunk revision 62520.

it uses function pointers to reduce code duplication; in case this
inappropriate here, i also have a pointerless version.

i familiar with c but not experienced, especially with the python ways
of writing c. most of the code is just adapted from other functions in
the same files, so it is probably, but should nevertheless checked with
special care.

i've also added test, but am not sure what has to be tested and what not.


compatibility
-------------

only cases in which division would fail without the patch are changed.
this will be a problem if (and only if) someone divides unknown objects
and waits for TypeError to be raised.
such behavior is probably rare.


[1] <mid:4813CD56.40800@eml.cc>,
http://mail.python.org/pipermail/python-list/2008-April/488406.html
[2] http://mail.python.org/pipermail/python-dev/2002-March/020604.html
msg70018 - (view) Author: (webograph) Date: 2008-07-19 13:00
this is the mentioned patch without the function pointers, in case it
better fits the python coding style.
msg75876 - (view) Author: Alexander Belopolsky (belopolsky) Date: 2008-11-14 17:43
I attaching webograph's patch updated to revision 67223 where I added a 
few tests.

I am +1 on the floor divide changes (allowing timedelta // timedelta), 
but I am not sure how true division should work if at all.  For the sake 
of argument, let's assume from __future__ import division or py3k.  
Currently: 

>>> timedelta(1)/2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'datetime.timedelta' and 
'int'
>>> timedelta(1)/timedelta(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'datetime.timedelta' and 
'datetime.timedelta'

With this patch, timedelta(1)/2 is still unsupported, but

>>> timedelta(1)/timedelta(2)
0.5

Note that this is probably correct behavior because timedelta/int true 
division does not make much sense, but there is a proposal (see issue1083) to make timedelta/int equivalent to timedelta//int.  (I am 
against issue1083 proposal and for webograph's approach, but this needs 
to be further discussed.)

Also, I've added a test that demonstrates the following behavior:

>>> int(timedelta.min/(timedelta.min//3))
2

This is not a bug in webograph's patch, but rather a bug in long true 
division, but it shows that true division may not be as useful as it 
seems.
msg75877 - (view) Author: STINNER Victor (haypo) Date: 2008-11-14 17:51
Why not also implementing divmod()? It's useful to split a timedelta 
into, for example, (hours, minutes, seconds):

def formatTimedelta(delta):
    """
    >>> formatTimedelta(timedelta(hours=1, minutes=24, seconds=19))
    '1h 24min 19sec'
    """
    hours, minutes = divmodTimedelta(delta, timedelta(hours=1))
    minutes, seconds = divmodTimedelta(minutes, timedelta(minutes=1))
    seconds, fraction = divmodTimedelta(seconds, timedelta(seconds=1))
    return "{0}h {1}min {2}sec".format(hours, minutes, seconds)

My implementation gives divmod(timedelta, timedelta) -> (long, 
timedelta). It's a little bit strange to get two different types in 
the result. The second return value is the remainder. My example works 
in the reverse order of the classical code:

def formatSeconds(seconds):
    """
    >>> formatTimedelta(1*3600 + 24*60 + 19)
    '1h 24min 19sec'
    """
    minutes, seconds = divmod(seconds, 60)
    hours, minutes = divmod(minutes, 60)
    return "{0}h {1}min {2}sec".format(hours, minutes, seconds)
    
About my new patch:
 - based on datetime_datetime_division_dupcode.patch
 - create divmod() operation on (timedelta, timedelta)
 - add unit tests for the division (floor and true division) and 
divmod
 - update the documentation for the true division and divmod
msg75878 - (view) Author: Alexander Belopolsky (belopolsky) Date: 2008-11-14 18:03
On Fri, Nov 14, 2008 at 12:51 PM, STINNER Victor <report@bugs.python.org> wrote:
>
> STINNER Victor <victor.stinner@haypocalc.com> added the comment:
>
> Why not also implementing divmod()? It's useful to split a timedelta
> into, for example, (hours, minutes, seconds):

I agree and in this case mod should probably be implemented too.

With your patch:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for %: 'datetime.timedelta' and
'datetime.timedelta'
msg75880 - (view) Author: Alexander Belopolsky (belopolsky) Date: 2008-11-14 18:15
Also, why not

>>> divmod(timedelta(3), 2)
(datetime.timedelta(1), datetime.timedelta(1))

?

And where do we stop? :-)

On Fri, Nov 14, 2008 at 1:02 PM, Alexander Belopolsky
<belopolsky@users.sourceforge.net> wrote:
> On Fri, Nov 14, 2008 at 12:51 PM, STINNER Victor 
<report@bugs.python.org> wrote:
>>
>> STINNER Victor <victor.stinner@haypocalc.com> added the comment:
>>
>> Why not also implementing divmod()? It's useful to split a timedelta
>> into, for example, (hours, minutes, seconds):
>
> I agree and in this case mod should probably be implemented too.
>
> With your patch:
>
>>>> timedelta(3)%timedelta(2)
> Traceback (most recent call last):
>  File "<stdin>", line 1, in <module>
> TypeError: unsupported operand type(s) for %: 'datetime.timedelta' and
> 'datetime.timedelta'
>
msg75881 - (view) Author: STINNER Victor (haypo) Date: 2008-11-14 18:28
Since timedelta(3) // 2 is already accepted, divmod should also accept 
integers (but not float).

With the last patch and "from __future__ import division", we support:
  timedelta // <timedelta or int>
  timedelta / timedelta
  divmod(timedelta, timedelta)

What do you think about:
  timedelta / <timedelta or int or float>  # only with __future__.divison
  timedelta // <timedelta or int>
  timedelta % <timedelta or int>
  divmod(timedelta, <timedelta or int>)
with:
  timedelta // int -> timedelta
  timedelta // timedelta -> int
  timedelta % int -> timedelta
  timedelta % timedelta -> int
  divmod(timedelta, int) -> (timedelta, timedelta)
  divmod(timedelta, timedelta) -> (int, timedelta)
  timedelta / <anything> -> float # __future__.divison
msg75882 - (view) Author: Alexander Belopolsky (belopolsky) Date: 2008-11-14 18:35
While I agree that divmod may be useful, your particular use case is
not convincing. The same can be done easier without divmod:

def formatTimedelta(delta):
   return "{0}h {1}min {2}sec".format(*str(delta).split(':'))

or you can convert delta to time using an arbitrary anchor date and
extract hms that way:

(1, 24, 19)

(depending on your needs you may want to add delta.days*24 to the hours)

On Fri, Nov 14, 2008 at 12:51 PM, STINNER Victor <report@bugs.python.org> wrote:
>
> STINNER Victor <victor.stinner@haypocalc.com> added the comment:
>
> Why not also implementing divmod()? It's useful to split a timedelta
> into, for example, (hours, minutes, seconds):
>
> def formatTimedelta(delta):
>    """
>    >>> formatTimedelta(timedelta(hours=1, minutes=24, seconds=19))
>    '1h 24min 19sec'
>    """
>    hours, minutes = divmodTimedelta(delta, timedelta(hours=1))
>    minutes, seconds = divmodTimedelta(minutes, timedelta(minutes=1))
>    seconds, fraction = divmodTimedelta(seconds, timedelta(seconds=1))
>    return "{0}h {1}min {2}sec".format(hours, minutes, seconds)
>
msg75883 - (view) Author: Alexander Belopolsky (belopolsky) Date: 2008-11-14 18:52
On Fri, Nov 14, 2008 at 1:28 PM, STINNER Victor <report@bugs.python.org> wrote:
..
> What do you think about:
>  timedelta / <timedelta or int or float>  # only with __future__.divison
>  timedelta // <timedelta or int>
>  timedelta % <timedelta or int>
>  divmod(timedelta, <timedelta or int>)
> with:
>  timedelta // int -> timedelta
already there

+1

+1

+1

timedelta % float -> timedelta (because int % float -> int works) ?

+1

+1

divmod(timedelta, float) -> (timedelta, timedelta) ?

-1

Only timedelta / timedelta should produce dimensionless numbers.
timedelta / <float or int> should be disallowed in true division mode.
 I am +0 on timedelta / timedelta -> float in true division mode.
msg75884 - (view) Author: Alexander Belopolsky (belopolsky) Date: 2008-11-14 18:59
Oops, the tracker ate some lines from e-mail.  Reposting through the 
web:

On Fri, Nov 14, 2008 at 1:28 PM, STINNER Victor <report@bugs.python.org> 
wrote:
..
> What do you think about:
>  timedelta / <timedelta or int or float>  # only with 
__future__.divison
>  timedelta // <timedelta or int>
>  timedelta % <timedelta or int>
>  divmod(timedelta, <timedelta or int>)
> with:
>  timedelta // int -> timedelta
already there

>  timedelta // timedelta -> int
+1

>  timedelta % int -> timedelta
+1

>  timedelta % timedelta -> int
+1

timedelta % float -> timedelta (because int % float -> int works) ?

>  divmod(timedelta, int) -> (timedelta, timedelta)
+1

>  divmod(timedelta, timedelta) -> (int, timedelta)
+1

divmod(timedelta, float) -> (timedelta, timedelta) ?

>  timedelta / <anything> -> float # __future__.divison
-1

Only timedelta / timedelta should produce dimensionless numbers.
timedelta / <float or int> should be disallowed in true division mode.
 I am +0 on timedelta / timedelta -> float in true division mode.
Reply
		
Forward
msg75892 - (view) Author: STINNER Victor (haypo) Date: 2008-11-14 21:59
> def formatTimedelta(delta):
>    return "{0}h {1}min {2}sec".format(*str(delta).split(':'))

OMG, this is ugly! Conversion to string and reparse the formatted text :-/ 
Your code doesn't work with different units than hours, minutes or seconds:

['4 days, 1', '32', '01']
>>> str(timedelta(hours=1, minutes=32, seconds=1, microseconds=2)).split(":")
['1', '32', '01.000002']

> or you can convert delta to time using an arbitrary anchor date
> and extract hms that way:

How? I don't understand your suggestion.

> (depending on your needs you may want to add delta.days*24 to the hours)

The goal of the new operators (timedelta / timedelta, divmod(timedelta, 
timedelta), etc.) is to avoid the use of the timedelta "internals" (days, 
seconds and microseconds attributes) and give a new "natural" way to process 
time deltas.
msg75894 - (view) Author: Alexander Belopolsky (belopolsky) Date: 2008-11-14 22:06
haypo> How? I don't understand your suggestion.

Sorry, another case of mail to tracker bug.  Here is what I wrote:

"""
.. you can convert delta to time using an arbitrary anchor date and
extract hms that way:

>>> x = datetime(1,1,1) + timedelta(hours=1, minutes=24, seconds=19)
>>> x.hour,x.minute,x.second
(1, 24, 19)

(depending on your needs you may want to add delta.days*24 to the hours)
"""

but tracker ate the '>>>' lines :-(
msg75895 - (view) Author: STINNER Victor (haypo) Date: 2008-11-14 23:07
@webograph: time_gmtime() and time_localtime() already use function 
pointer. I prefer function pointer than code duplication!
msg75908 - (view) Author: Mark Dickinson (mark.dickinson) Date: 2008-11-15 10:08
> timedelta / <float or int> should be disallowed in true division mode.

I don't understand this;  why should the division mode affect
division operations involving timedeltas at all?  The meaning
of "/" is unaffected by the division mode for float/float or
float/int;  why should timedeltas be any different?

I vote +1 for timedelta/timedelta and timedelta/float (regardless
of division mode).  timedelta / timedelta is the one obvious way
to find out 'how many A's in B', and the one that it's natural
to try first, before looking for (timedelta -> float) conversion
methods.
msg75909 - (view) Author: Mark Dickinson (mark.dickinson) Date: 2008-11-15 10:25
By the way, I assume that any plan to add this division would also include 
adding the inverse operation:

timedelta * float -> timedelta.

It wouldn't make a whole lot of sense to have one without the other.
msg75913 - (view) Author: STINNER Victor (haypo) Date: 2008-11-15 12:36
Some examples to help the choice (using the last patch).

int
---

2L
>>> print dt2 * 2
3:08:38
>>> print dt1 - dt2 * 2
0:51:22
>>> divmod(dt1, dt2)
(2L, datetime.timedelta(0, 3082))
>>> print timedelta(0, 3082)
0:51:22

In 4 hours, you can watch the movie twice, and then your have 51 minutes left.

Operations used:
 - timedelta // timedelta
 - timedelta * int
 - divmod(timedelta, timedelta)

float
-----

0.21258172822053367
>>> "Progress: %.1f%%" % ((dt1 / dt2) * 100.0)
'Progress: 21.3%'
>>> dt2 * 0.75
...
TypeError: unsupported operand type(s) for *: 'datetime.timedelta' and 'float'
>>> print (dt2 * 3) // 4
1:10:44.250000

If you are seen this movie since 20 minutes, you're at 21% of the total. If 
you want to jump to 75%, it will be at 1:10:44.

Note: timedelta * float is not implemented yet.

Operations used:
 - timedelta / timedelta
 - timedelta * int and timedelta // int (because timdelta / float is 
   not implemented yet)
msg75917 - (view) Author: Alexander Belopolsky (belopolsky) Date: 2008-11-15 21:08
On Sat, Nov 15, 2008 at 5:08 AM, Mark Dickinson <report@bugs.python.org> wrote:
>
> Mark Dickinson <dickinsm@gmail.com> added the comment:
>
>> timedelta / <float or int> should be disallowed in true division mode.
>
> I don't understand this;  why should the division mode affect
> division operations involving timedeltas at all?

Here is how I think about this:  timedeltas are integers in units of
time.  For simplicity, let's assume we always express timedeltas in
microseconds (us), so timedeltas are things like 1us, 2us, etc.  As
with integers,  we can define true division (/) and floor division
(//) so that 3us/2us = 1.5 and 3us//2us = 1.  Note that the result is
dimensionless.  Similarly, you can divide timedeltas by  dimensionless
integers: 3us/2 = 1.5us and 3us//2 = 1us.  However in python you
cannot have a timedelta representing 1.5us, so timedelta(0, 0, 3)/2
should be en error.  In order to have a timedelta/int true division,
we would need to have another type floattimedelta which would be a
floating point number in units of time.

>  The meaning of "/" is unaffected by the division mode for float/float or
> float/int;  why should timedeltas be any different?

Because they are integers.  If we had a floattimedelta type that would
store timestamp as a float, its division would rightfully not be
affected by the division mode.

> I vote +1 for timedelta/timedelta and timedelta/float (regardless
> of division mode).

What do you vote  timedelta/timedelta should produce in floor division
mode: an int or a float? and what should  timedelta/float produce: a
timedelta or a float?
msg77646 - (view) Author: STINNER Victor (haypo) Date: 2008-12-11 23:36
I'm finally opposed to datetime.totimedelta() => float, I 
prefer .totimedelta() => (second, microsecond) which means (int,int). 
But I like timedelta/timedelta => float, eg. to compute a progression 
percent. Anyone interested by my last patch (implement 
timedelta/timedelta and divmod(timedelta, timedelta)?
msg83416 - (view) Author: Fredrik Johansson (fredrikj) Date: 2009-03-10 06:03
I think datetime division would be a fine application for Fractions.
msg83447 - (view) Author: Tennessee Leeuwenburg (tleeuwenburg@gmail.com) Date: 2009-03-10 21:15
Hi all,

I'm trying to help out by reviewing issues in the tracker... so this is 
just a first attempt and I hope it is somewhat useful. This issue covers 
a number of discrete functional changes. I here review each in turn:

1) Allow truediv, the operator '/', to be applied to two timedeltas 
(e.g. td1 / td2). The return value is a float representing the number of 
times that td2 goes into td1.

Evaluation: Since both time deltas are quantitative values of the same 
unit, it makes sense they should be able to support basic math 
operations. In this case, operation to be added is '/' or truediv. I 
would personally find this functionality useful, and believe it is a 
natural addition to the code.

Recommendation: That this functionality be recommended for development

2) Allow truediv to be applied to a timedelta and an int or float. The 
return value is a timedelta representing the fractional proportion of 
the original timedelta.

Evaluation: This makes total sense.
Recommendation: That this functionality be recommended for development

2) Allow divmod, the operator '%', to be applied to two timedeltas (e.g. 
td1 % td2). I think there is some debate here about whether the return 
value be an integer in microsends, or a timedelta. I personally believe 
that a timedelta should be returned, representing the amount of time 
remaining after (td1 // td2)  * td2 has been subtracted.

The alternative is an integer, but due to a lack of immediate clarity 
over what time quanta this integer represents, I would suggest returning 
a unit amount. There is also some discussion of returning (long, 
timedelta), but I personally fail to see the merits of returning the 
long without some unit attached.

3) Allow divmod, the operator '%', to be applied to a timedelta and an 
integer or float. (e.g. <timedelta> % 3). I'm not quite as sold on this.  
I suggest that more discussion is required to flesh this out further.


A patch has been attached which implements some of this behaviour. I 
would suggest that it's valuable to start doing this work in discrete 
chunks so that progress can be begun before the issues under debate are 
resolved. I suggest that it would be appropriate to create three smaller 
issues, each tackling just one piece of functionality. This should make 
the changes more atomic and the code patch simpler.

-T
msg83453 - (view) Author: Alexander Belopolsky (belopolsky) Date: 2009-03-11 03:33
On Tue, Mar 10, 2009 at 5:15 PM, Tennessee Leeuwenburg
<report@bugs.python.org> wrote:
..
> 2) Allow divmod, the operator '%', to be applied to two timedeltas (e.g.
> td1 % td2). I think there is some debate here about whether the return
> value be an integer in microsends, or a timedelta. I personally believe
> that a timedelta should be returned, representing the amount of time
> remaining after (td1 // td2)  * td2 has been subtracted.
>
> The alternative is an integer, but due to a lack of immediate clarity
> over what time quanta this integer represents, I would suggest returning
> a unit amount. There is also some discussion of returning (long,
> timedelta), but I personally fail to see the merits of returning the
> long without some unit attached.
>

I don't think this alternative was ever seriously considered and no
patch was ever proposed to do it that way.  Victor's latest patch
implements divmod(timedelta, timedelta) -> (int, timedelta) and
therefore timedelta % timedelta -> timedelta.
msg83936 - (view) Author: (webograph) Date: 2009-03-21 12:38
i don't think this can be solved in a way that is independent of the
chosen unit, as it requires a concept of "whole time-units" (as in
"whole numbers"); whether these be seconds or minutes would be
completely arbitrary.

(`5 minutes % 3 = 0 minutes` would be true if based on seconds because
`5 minutes = 3 * 100 seconds + 0 minutes` but `5 minutes % 3 = 2
minutes` based on minutes because `5 minutes = 3 * 1 minute + 2 minutes`.)
msg84131 - (view) Author: Jess Austin (jess.austin) Date: 2009-03-25 00:04
A comment on the two most recent patches...  For both of these, we can
do the following:

>>> from datetime import timedelta
>>> td = timedelta(12)
>>> td
datetime.timedelta(12)
>>> td //= 3
>>> td
datetime.timedelta(4)
>>> td //= timedelta(2)
>>> td
2          # CHANGED VARIABLE TYPE!

I think the last operation will trap unsuspecting programmers, and
provide no benefit for the savvy.  There really is no reason to allow an
in-place operation like this to change the type of the variable so
drastically.  (That is, I realize a similar thing could happen with ints
and floats, but it seems worse with timedeltas and ints.)  I feel the
last operation should raise a TypeError, even though it would be quite
valid for a non-in-place operation.
msg84151 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) Date: 2009-03-25 08:45
Well, this already happen with other types:

>>> a   = 100
>>> a //= 2.0
>>> a
50.0

>>> d  = datetime.datetime.now()
>>> d -= datetime.datetime.now()
>>> d
datetime.timedelta(-1, 86391, 609000)

See http://docs.python.org/reference/datamodel.html#object.__iadd__
"... return the result (which could be, but does not have to be, self) ..."
History
Date User Action Args
2009-04-08 11:43:36tleeuwenburg@gmail.comsetassignee: tleeuwenburg@gmail.com
2009-03-25 08:45:09amaury.forgeotdarcsetmessages: + msg84151
2009-03-25 00:04:23jess.austinsetnosy: + jess.austin
messages: + msg84131
2009-03-21 12:38:01webographsetmessages: + msg83936
2009-03-11 03:33:41belopolskysetmessages: + msg83453
2009-03-10 21:15:55tleeuwenburg@gmail.comsetmessages: + msg83447
2009-03-10 06:03:28fredrikjsetnosy: + fredrikj
messages: + msg83416
2009-03-10 02:49:42tleeuwenburg@gmail.comsetnosy: + tleeuwenburg@gmail.com
2008-12-11 23:36:03hayposetmessages: + msg77646
2008-11-15 21:08:13belopolskysetmessages: + msg75917
2008-11-15 12:36:12hayposetmessages: + msg75913
2008-11-15 10:25:59mark.dickinsonsetmessages: + msg75909
2008-11-15 10:08:07mark.dickinsonsetnosy: + mark.dickinson
messages: + msg75908
2008-11-14 23:07:30hayposetmessages: + msg75895
2008-11-14 22:06:24belopolskysetmessages: + msg75894
2008-11-14 21:59:43hayposetmessages: + msg75892
2008-11-14 18:59:46belopolskysetmessages: + msg75884
2008-11-14 18:52:12belopolskysetmessages: + msg75883
2008-11-14 18:35:53belopolskysetmessages: + msg75882
2008-11-14 18:28:40hayposetmessages: + msg75881
2008-11-14 18:15:01belopolskysetmessages: + msg75880
2008-11-14 18:03:00belopolskysetmessages: + msg75878
2008-11-14 17:51:29hayposetfiles: + timedelta_true_divide_divmod.patch
nosy: + haypo
messages: + msg75877
2008-11-14 17:46:00belopolskysetnosy: + jribbens, amaury.forgeotdarc
versions: + Python 3.1, Python 2.7
2008-11-14 17:43:44belopolskysetfiles: + timedeltadiv.parch
nosy: + belopolsky
messages: + msg75876
2008-07-19 13:00:46webographsetfiles: + datetime_datetime_division_dupcode.patch
messages: + msg70018
2008-04-27 21:04:03webographcreate