views:

857

answers:

2

I understand that seconds and microseconds are probably represented separately in datetime.timedelta for efficiency reasons, but I just wrote this simple function:

def to_seconds_float(timedelta):
    """Calculate floating point representation of combined
    seconds/microseconds attributes in :param:`timedelta`.

    :raise ValueError: If :param:`timedelta.days` is truthy.

        >>> to_seconds_float(datetime.timedelta(seconds=1, milliseconds=500))
        1.5
        >>> too_big = datetime.timedelta(days=1, seconds=12)
        >>> to_seconds_float(too_big) # doctest: +ELLIPSIS
        Traceback (most recent call last):
        ...
        ValueError: ('Must not have days', datetime.timedelta(1, 12))
    """
    if timedelta.days:
        raise ValueError('Must not have days', timedelta)
    return timedelta.seconds + timedelta.microseconds / 1E6

This is useful for things like passing a value to time.sleep or select.select. Why isn't something like this part of the datetime.timedelta interface? I may be missing some corner case. Time representation seems to have so many non-obvious corner cases...

I rejected days right out to have a reasonable shot at some precision (I'm too lazy to actually work out the math ATM, so this seems like a reasonable compromise ;-).

+6  A: 

A Python float has about 15 significant digits, so with seconds being up to 86400 (5 digits to the left of the decimal point) and microseconds needing 6 digits, you could well include the days (up to several years' worth) without loss of precision.

A good mantra is "pi seconds is a nanocentury" -- about 3.14E9 seconds per 100 years, i.e. 3E7 per year, so 3E13 microseconds per year. The mantra is good because it's memorable, even though it does require you to do a little mental arithmetic afterwards (but, like spinach, it's GOOD for you -- keeps you nimble and alert!-).

The design philosophy of datetime is somewhat minimalist, so it's not surprising it omits many possible helper methods that boil down to simple arithmetic expressions.

Alex Martelli
Understood that there are many *possible* helper methods, but this one seems particularly useful given the interfaces of other stdlib functions -- do you think it's worth proposing for inclusion? It'll be your call anyway. ;-)
cdleary
Honestly I don't make time for the hundreds-of-posts threads on python-dev much any more -- it's become just too time-consuming:-(. I do suggest you go to python-ideas and try putting together a PEP with a set of extra helper methods that can prove popular and not controversial (my own favorite would be a totimestamp method -- having only fromtimestamp is absurd!), then air it on python-dev and -- *duck*!-)
Alex Martelli
I should vote you down for the spinach comment. :) I like the mantra btw, so I voted up instead.
Scott
+1  A: 

Your concern for precision is misplaced. Here's a simple two-liner to calculate roughly how many YEARS you can squeeze into what's left of the 53 bits of precsion in an IEEE754 64-bit float:

>>> import math
>>> 10 ** (math.log10(2 ** 53) - math.log10(60 * 60 * 24) - 6) / 365.25
285.42092094268787
>>>

Watch out for round-off; add the smallest non-zero numbers first:

return timedelta.seconds + timedelta.microseconds / 1E6 + timedelta.days * 86400
John Machin
I'm not sure it's misplaced -- even knowing this, you now have to determine whether allowing a number of days <= 285 (which is a pretty weird restriction number for people to remember) is better than disallowing them as a rule (easy to remember). Thanks for calculating it out though -- that's the work I was hoping to avoid in the first place. :-)
cdleary
The point of my answer was that you can still have microsecond precision if you add in 280-odd years. However people who are working with years probably don't care about microseconds anyway. What restriction? Surely you weren't planning to check for 285 years in your code? Sheesh * 2 ** 53 !! If the timedelta is say 300 * 366 days and the seconds and microseconds are zero, no precision would be lost. Remember the F is short for Floating, not Fixed.
John Machin
I understand, I just thought that calculating whether the precision was sufficient on the fly was significantly more complicated than limiting it in the most significant dimension. (It's also easier to know how to fix the error condition.) I guess you're right, though -- if I wanted to make this function more serious I could raise a ValueError when determining I couldn't meet the required precision and let the user deal with it from there.
cdleary
What error? what required precision? Calculating precision on the fly hasn't been discussed and is totally unwarranted. Raise a Value Error? With what message?? All that you said is that you want a timedelta-to-seconds method (no restriction inherent in the name) ... but you're talking about imposing ARBITRARY conditions on the input, and haven't even spelled out what those conditions are!
John Machin
Well, I was thinking that you would raise an error if the recovery of the minimum unit (microseconds) would be changed beyond some epsilon (like half a microsecond). On reflection, I guess that's silly -- it's just a hazard that comes with all floating point numbers that the user has to be aware of.
cdleary
That's right ... so your proposed method is back to a one-liner with a fairly obvious and uncomplicated implementation; possibly not a candidate for inclusion in the Python core. Do take Alex's advice and duck :-)
John Machin