# Format seconds into the two biggest units of time

I wrote a function to convert seconds into the two biggest units of time and format it into a string. What I mean is that e.g. 134 second would be 02m14s and 3665 seconds would become 1h1m. Here is what I did:

def pprint_time(seconds):
m, s = divmod(seconds, 60)
h, m = divmod(m, 60)
d, h = divmod(h, 24)
format_str = lambda v1, v2, p: f"{v1:0>2}{p}{v2:0>2}{p}"
if d > 0:
return format_str(d, h, ("d","h"))
elif h > 0:
return format_str(h, m, ("h","m"))
elif m > 0:
return format_str(m, s, ("m","s"))
else:
return f" {s:0>2}sec"


I wonder if there is a more "pythonic" way of doing this since the if-elif-else statement does feel a bit clunky.

In general I think your code is pretty good. It is a simple function, and it does not need to get fancy.

I do think one bit could be a bit cleaner. I believe this format string:

format_str = lambda v1, v2, p: f"{v1:0>2}{p}{v2:0>2}{p}"
format_str(d, h, ("d","h"))


would be simpler and therefore clearer as:

format_str = "{:0>2}{}{:0>2}{}"
format_str.format(d, 'd', h, 'h')


I think the lambda here is not helping readability.

## More Pythonic?

For this simple case I don't think you really need anything more Pythonic. But let's have some fun and illustrate using some Python libraries.

### datetime library

First thing to consider is maybe using standard Python lib for doing some of the date and time math. The library could build those values for you like so:

import datetime as dt

# build a datetime from the number of seconds passed in
a_time = dt.datetime.utcfromtimestamp(seconds)

# get d, h, m, s
d, h, m, s = dt.timedelta(seconds=seconds).days, a_time.hour, a_time.minute, a_time.second


### Stacked ifs

You asked specifically about the stacked ifs. In this case the ifs are not stacked very deep, so what follows is rather severe overkill :-)

Here is a general way to get the elements following the first non-zero element.

# get values fllowing first non zero value and their labels
non_zero = tuple(it.dropwhile(lambda x: not x, zip(values, 'dhms')))


Yikes, this is that an eyesore! But is does show how to get the elements following the first non-zero element in a general way, IE: No stacked ifs needed.

### How does that work?

Let's break it down a bit. Starting from the inside:

zip(values, 'dhms')


This will produce a sequence of tuples looking something like:

[(0, 'd'), (1, 'h'), (1, 'm'), (5, 's')]


Note how the use of zip() allows the labels to be associated with their elements. This allows the next step to keep the labels with their values.

Then:

it.dropwhile(lambda x: not x, zip(values, 'dhms')))


uses itertools.dropwhile() to drop the leading tuples which start with 0. Finally:

non_zero = tuple(it.dropwhile(lambda x: not x, zip(values, 'dhms')))


will produce a tuple so that we can get the number of elements to help produce the format string:

# a format string
format_str = "{:0>2}{}{:0>2}{}" if len(non_zero) > 1 else " {:0>2}sec"


And then bring it all together with:

# format the string
return format_str.format(*it.chain.from_iterable(non_zero or ((0, 0),)))


itertools.chain.from_iterable() flattens the non_zero tuple, to make the format() a little easier:

### Complete recast function

import datetime as dt
import itertools as it

def pprint_time2(seconds):

# build a datetime from the number of seconds passed in
a_time = dt.datetime.utcfromtimestamp(seconds)

# get d, h, m, s
values = dt.timedelta(seconds=seconds).days, a_time.hour, a_time.minute, a_time.second

# get values following first non zero value and their labels
non_zero = tuple(it.dropwhile(lambda x: not x, zip(values, 'dhms')))

# a format string
format_str = "{:0>2}{}{:0>2}{}" if len(non_zero) > 1 else " {:0>2}sec"

# format the string
return format_str.format(*it.chain.from_iterable(non_zero or ((0, 0),)))

• Thanks just what I was looking for! I know that my function isn't really that cumbersome but I just wanted to know if there was some shorter (albeit maybe dumber) way of doing it. Just for fun and for the future. – Alexander Simko Sep 2 '18 at 13:01