What is the best way to write a function that converts an integer of minutes into a string formatted in “X hour(s) Y minute(s)”?

StackOverflow https://stackoverflow.com/questions/10425081

Question

I currently have my Django custom template filter like this:

from django import template

register = template.Library()

@register.filter
def duration(value):
    hours   = value / 60
    minutes = value % 60

    hours_string   = str(hours)   + (" hours"   if hours   > 1 else " hour"  ) if hours   else ""
    minutes_string = str(minutes) + (" minutes" if minutes > 1 else " minute") if minutes else ""

    return ' '.join("{hours} {minutes}".format(hours=hours_string, minutes=minutes_string).split())

Some possible outputs:

1 hour 25 minutes
2 hours
45 minutes
3 hours 1 minute

The ' '.join(....split()) trick was something I got from this solution. I realize I only need to check for the extra string at either the beginning or the end of the string (for when it's 0 hours or 0 minutes), but it seems to do the trick and maybe it's easier to read than regular expression to some people.

I also feel that I am violating DRY since I am repeating almost the exact same if-then block twice. I was thinking of something like...

for i, unit in enumerate([hours, minutes]):
    # Do some code.

return ' '.join("{hours} {minutes}".format(hours=value[0], minutes=value[1]).split())

but I couldn't figure out how to choose between "hour(s)" or "minute(s)" without another if-then block. Also the two short-hand if-then per line also looks a bit ugly too...

I'll appreciate any suggestions or tips. Thanks!

Était-ce utile?

La solution

import datetime
def pprint(val):
   if val == 1:
      return '1 minute'
   x = str(datetime.timedelta(minutes=val))[:-3].split(':')
   r = ''
   if int(x[0]):
       r += x[0]+' hours' if int(x[0]) > 1 else x[0]+' hour'
   if int(x[1]):
       r += ' %s minutes' % int(x[1]) if int(x[1]) > 1 else ' %s minute' % int(x[0])
   return r.strip()

Sample run:

>>> pprint(65)
'1 hour 5 minutes'
>>> pprint(1)
'1 minute'
>>> pprint(60)
'1 hour'
>>> pprint(61)
'1 hour 1 minute'

You can easily expand this to include days as well, but since you specifically mentioned only hours and minutes I adjusted the method.

The main heavy lifting is done by datetime.timedelta, the rest is just string parsing.

Autres conseils

I use this for modular unit conversions, including the sort you've described. I wrote it while working for UCI, and UCI gave permission to distribute it under a specific opensource license:

http://stromberg.dnsalias.org/~strombrg/modunits/

You can use it as a shell-callable for POSIX shell scripts, or it can be used as a python module via import.

I consider it production-quality, and indeed use it in a production role (backups) regularly.

This is my very first attempt at Python, so if this is totally missing your actual goal or is wrought with errors, lemme know and I'll delete.

To keep things DRY, and to eliminate the output of "0 hours" or "1 minutes", this should work, so long as we accept the premise that the base unit (in this case minute and hour) can be pluralized by simply adding an 's'. Anything more complex and you'll want a python ninja:

def format_output(value, unit):
    if value == 0:
        return ""

    singular_output = str(value) + " " + unit

    if value == 1:
        return singular_output
    elif value > 1:
        return singular_output + "s"

def duration(value):
    hours   = value // 60
    minutes = value % 60

    formatted_string = format_output(hours, "hour") + " " + format_output(minutes, "minute")
    return formatted_string.strip() 

The one condition the above does not account for is if duration is 0 or blank, which I think should return "0 minutes", but that is an easy fix that is best left to your needs.

Also, I added in the strip() method, otherwise you'll be left with leading or trailing space if one of the two units comes out to zero.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top