HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Efficient human readable timedelta

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
humanefficientreadabletimedelta

Problem

I wanted to write a human readable datetime.timedelta that can be used in log files.

Eg, "Report issued 1 hour, 44 minutes, 20 seconds ago"

I noticed that casting a timedelta to str() generates something almost like what I want, but not quite.

To this end I wrote this:

def verbose_timedelta(delta):
    hours, remainder = divmod(delta.seconds, 3600)
    minutes, seconds = divmod(remainder, 60)
    dstr = "%s day%s" % (delta.days, "s"[delta.days==1:])
    hstr = "%s hour%s" % (hours, "s"[hours==1:])
    mstr = "%s minute%s" % (minutes, "s"[minutes==1:])
    sstr = "%s second%s" % (seconds, "s"[seconds==1:])
    dhms = [dstr, hstr, mstr, sstr]
    for x in range(len(dhms)):
        if not dhms[x].startswith('0'):
            dhms = dhms[x:]
            break
    dhms.reverse()
    for x in range(len(dhms)):
        if not dhms[x].startswith('0'):
            dhms = dhms[x:]
            break
    dhms.reverse()
    return ', '.join(dhms)


Essentially, it's shaving off both ends of a list to make the results more meaningful.

The code above feels clunky though. Is there a more "Pythonic" way to do it? I'm using Python 2.7.3.

Solution


  • Use a ternary operator in "s"[seconds==1:].



  • Use a generator expression to replace the xstr = "%s... lines.



  • The two for loops should use enumerate(), i.e. they could be for s, i in range(...).



  • The two for loops should be moved into a for _ in range(2):.



  • i is preferred over x when using an i​ncrementing i​ndex counter.



  • The filtering of the redundant strings which the for-loop does could be done earlier so that the number to string code can be modified but the filtering code will not require adjustments.



PS: I have implemented a similar function here:

days, rem = divmod(seconds, 86400)
    hours, rem = divmod(rem, 3600)
    minutes, seconds = divmod(rem, 60)
    if seconds < 1:seconds = 1
    locals_ = locals()
    magnitudes_str = ("{n} {magnitude}".format(n=int(locals_[magnitude]), magnitude=magnitude)
                      for magnitude in ("days", "hours", "minutes", "seconds") if locals_[magnitude])
    eta_str = ", ".join(magnitudes_str)

Code Snippets

days, rem = divmod(seconds, 86400)
    hours, rem = divmod(rem, 3600)
    minutes, seconds = divmod(rem, 60)
    if seconds < 1:seconds = 1
    locals_ = locals()
    magnitudes_str = ("{n} {magnitude}".format(n=int(locals_[magnitude]), magnitude=magnitude)
                      for magnitude in ("days", "hours", "minutes", "seconds") if locals_[magnitude])
    eta_str = ", ".join(magnitudes_str)

Context

StackExchange Code Review Q#37285, answer score: 3

Revisions (0)

No revisions yet.