3
\$\begingroup\$

Quite often, I find myself measuring how long some part of my code takes to complete. I normally do that by storing current time using time.time() and then subtracting time after the code is done.

This normally gives me a float that I can then format and print for debugging purposes. This is the function that I have for time formatting:

def time_format(delta: float) -> str:
    output = []
    decimal, integer = math.modf(delta)

    if integer:
        minutes, seconds = divmod(int(integer), 60)

        if minutes:
            output.append("%sm" % minutes)

        if seconds:
            output.append("%ss" % seconds)

    decimal, integer = math.modf(decimal * 1000)
    if integer:
        output.append("%sms" % int(integer))

    decimal, integer = math.modf(decimal * 1000)
    if integer:
        output.append("%sμs" % int(integer))

    decimal, integer = math.modf(decimal * 1000)
    if integer:
        output.append("%sns" % int(integer))

    return ", ".join(output)

I have also been told that the time module has most precision when it comes to time measuring so that's why I'm using it, instead of something like datetime which has nice formatting tools built in.

How could I improve my time formatting function? Are there any built in formatting tools that I'm not aware of? (Searching stack overflow leads me only to questions about datetime.timedelta)

\$\endgroup\$
  • \$\begingroup\$ Hello and welcome to CR! You might be interested in this as well \$\endgroup\$ – Grajdeanu Alex. Feb 25 at 20:32
  • \$\begingroup\$ I agree that timeit is great for measuring the time it takes for a function, but I'm more interested in measuring time between two arbitrary points in my code, sometimes hundred lines appart. My current approach allows to add/remove code to what I'm measuring by simply shifting the time measuring lines around. I'm not sure if that's possible with timeit module. \$\endgroup\$ – Mantas Kandratavicius Feb 25 at 20:54
  • \$\begingroup\$ It might be overkill but I suggest profilers like this one and whatever this graph thing is called \$\endgroup\$ – Benoît Pilatte Feb 25 at 21:42
  • \$\begingroup\$ Did you know you can build a timedelta(seconds=delta) and work from there? \$\endgroup\$ – Mathias Ettinger Feb 26 at 7:10
  • \$\begingroup\$ Yeah but I'm more interested in the formatting of the float part of the delta which isn't really suported by the timedelta object. \$\endgroup\$ – Mantas Kandratavicius Feb 26 at 7:14
2
\$\begingroup\$

There is indeed a function for that.

>>> from datetime import timedelta 
>>> print(timedelta(seconds=1239.123456))
0:20:39.123456

Now, if I understand you correctly, you want a divider for the millisecond.

from datetime import timedelta 
def f(secs):
    t=str(timedelta(seconds=secs))
    index = t.find('.') + 4
    return t[:index] + ' ' + t[index:]
print(f(1239.123456))
# 0:20:39.123 456

I don't like that everything is hardcoded, if you want to use it for different applications (or even languages), you might want to generalize your bases and names.

Here is a generic way of doing any formatting you want:

def prod(f):
    total = 1
    l = [1]
    for i in range(len(f)-1, 0, -1):
        total *= f[i][0]
        l.append(total)
    return reversed(l)


def format_time(number, f=((24, ':'), (60, ':'), (60, '.'), (1000, ' '), (1000, ''))):
    return ''.join(
        (f"{(number//div)%base:0>{len(str(base-1))}}" + delimiter)
        if number//div else ''
        for div, (base, delimiter) in zip(prod(f), f)
    )
formatting = (
    (24, ' hours '),
    (60, ' minutes '),
    (60, ' seconds '),
    (1000, ' milliseconds '),
    (1000, ' nanoseconds'),
)

print(
    format_time(1551198373998173),
    format_time(1551198373998173, formatting),
    format_time(1551739, formatting),
    sep="\n"
)

The result is:

16:26:13.998 173
16 hours 26 minutes 13 seconds 998 milliseconds 173 nanoseconds
01 seconds 551 milliseconds 739 nanoseconds

What are it's advantages, you can do any formatting in any base, with any names. We could even do a base 2 converter.

binary_formatting = (((2, ' '),)+((2, ''),)*3)*10 + ((2, ''),)

print(
    format_time(155933900, binary_formatting),
    format_time(3279, binary_formatting),
    sep="\n"
)
1001 0100 1011 0101 1100 1100 1100
1100 1100 1111

Other than that, if your code need to accomplish a single purpose and it does it well, good for you.

\$\endgroup\$
  • \$\begingroup\$ As I wrote in my question, I'm fully aware that you can format the timedelta object, however precision is important for me, which is why I wrote my own formatter since timedelta doesn't have an in-built formatting for small time deltas (<1 sec). There's not much difference between simply printing 0.000123 and 0:00:00.000123 (the "formatted" version). \$\endgroup\$ – Mantas Kandratavicius Feb 25 at 21:59
  • \$\begingroup\$ I updated my answer \$\endgroup\$ – Benoît Pilatte Feb 26 at 17:03
  • \$\begingroup\$ Your answer does produce similar behaviour to my formatting function (though hours, minutes etc still appear even if equal to 0) but I'm more or less looking for criticizm about my own formatter instead of writing a brand new one. \$\endgroup\$ – Mantas Kandratavicius Feb 26 at 17:36
  • \$\begingroup\$ I've updated my code to fix that. If your code does what you want it to do, that's good, I was just showing how I would have done it and why. \$\endgroup\$ – Benoît Pilatte Feb 26 at 18:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.