28

Is it possible to easily format seconds as a human-readable time in bash?

I don't want to format it as a date, but as the number of days/hours/minutes, etc...

  • Could you provide an example/several examples please? – gabe. Dec 16 '11 at 22:51
  • 1
    Are you saying you have an interval, or a date since the epoch? – Paul Tomblin Dec 16 '11 at 23:22
40

You can use something like this:

function displaytime {
  local T=$1
  local D=$((T/60/60/24))
  local H=$((T/60/60%24))
  local M=$((T/60%60))
  local S=$((T%60))
  (( $D > 0 )) && printf '%d days ' $D
  (( $H > 0 )) && printf '%d hours ' $H
  (( $M > 0 )) && printf '%d minutes ' $M
  (( $D > 0 || $H > 0 || $M > 0 )) && printf 'and '
  printf '%d seconds\n' $S
}

Examples:

$ displaytime 11617
3 hours 13 minutes and 37 seconds
$ displaytime 42
42 seconds
$ displaytime 666
11 minutes and 6 seconds
10

Easiest and cleanest way is this one liner (here assuming GNU date):

If the number of seconds is, say:

seconds=123456789 # as in one of the answers above

eval "echo $(date -ud "@$seconds" +'$((%s/3600/24)) days %H hours %M minutes %S seconds')"

--> output: 1428 days 21 hours 33 minutes 09 seconds

6

Credit goes to Stéphane Gimenez but if someone would like to display seconds only if a period is less than a minute here is my modified version that I use (also with fixed pluralization):

converts()
{
    local t=$1

    local d=$((t/60/60/24))
    local h=$((t/60/60%24))
    local m=$((t/60%60))
    local s=$((t%60))

    if [[ $d > 0 ]]; then
            [[ $d = 1 ]] && echo -n "$d day " || echo -n "$d days "
    fi
    if [[ $h > 0 ]]; then
            [[ $h = 1 ]] && echo -n "$h hour " || echo -n "$h hours "
    fi
    if [[ $m > 0 ]]; then
            [[ $m = 1 ]] && echo -n "$m minute " || echo -n "$m minutes "
    fi
    if [[ $d = 0 && $h = 0 && $m = 0 ]]; then
            [[ $s = 1 ]] && echo -n "$s second" || echo -n "$s seconds"
    fi  
    echo
}

An alternative example in POSIX:

converts(){
    t=$1

    d=$((t/60/60/24))
    h=$((t/60/60%24))
    m=$((t/60%60))
    s=$((t%60))

    if [ $d -gt 0 ]; then
            [ $d = 1 ] && printf "%d day " $d || printf "%d days " $d
    fi
    if [ $h -gt 0 ]; then
            [ $h = 1 ] && printf "%d hour " $h || printf "%d hours " $h
    fi
    if [ $m -gt 0 ]; then
            [ $m = 1 ] && printf "%d minute " $m || printf "%d minutes " $m
    fi
    if [ $d = 0 ] && [ $h = 0 ] && [ $m = 0 ]; then
            [ $s = 1 ] && printf "%d second" $s || printf "%d seconds" $s
    fi
    printf '\n'
}
  • This worked really well. I simply pasted the function into my script and ran convert $s to get pretty output. – flickerfly Oct 19 '16 at 19:25
3

I'd do it like this:

$ seconds=123456789; echo $((seconds/86400))" days "$(date -d "1970-01-01 + $seconds seconds" "+%H hours %M minutes %S seconds")
1428 days 21 hours 33 minutes 09 seconds
$

Here's the one liner above, broken down so that it's easier to understand:

$ seconds=123456789
$ echo $((seconds/86400))" days"\
     $(date -d "1970-01-01 + $seconds seconds" "+%H hours %M minutes %S seconds")

In the above I'm echoing out the output of another command that's run inside the $( ... ) sub-command. That sub-command is doing this, calculating the number of days (seconds/86400), then using the date command in another sub-command $(date -d ... ), to generate the hours, minutes, and seconds for a given number of seconds.

2

I modified the displaytime function above... as follows:

seconds2time ()
{
   T=$1
   D=$((T/60/60/24))
   H=$((T/60/60%24))
   M=$((T/60%60))
   S=$((T%60))

   if [[ ${D} != 0 ]]
   then
      printf '%d days %02d:%02d:%02d' $D $H $M $S
   else
      printf '%02d:%02d:%02d' $H $M $S
   fi
}

because I always want to see HH:MM:SS, even if they are zeros.

1

I'm building on atti's answer which I liked as an idea.

You can do this with the bash builtin printf which will take the seconds since the epoch as an argument. No need to fork to run date.

You have to set the timezone to UTC for printf because it formats the time in your local timezone and you will get the wrong answer if you are not in UTC time.

$ seconds=123456789
$ TZ=UTC printf "%d days %(%H hours %M minutes %S seconds)T\n" $((seconds/86400)) $seconds
1428 days 21 hours 33 minutes 09 seconds

In my local time (which is currently NZDT - +1300) the answer is wrong if I do not set the timezone

$ seconds=123456789
$ printf "%d days %(%H hours %M minutes %S seconds)T\n" $((seconds/86400)) $seconds
1428 days 09 hours 33 minutes 09 seconds

With and without setting the timezone

$ seconds=$(( 3600 * 25))
$ printf "%d days %(%H hours %M minutes %S seconds)T\n" $((seconds/86400)) $seconds
1 days 13 hours 00 minutes 00 seconds

$ TZ=UTC printf "%d days %(%H hours %M minutes %S seconds)T\n" $((seconds/86400)) $seconds
1 days 01 hours 00 minutes 00 seconds
  • Note that bash's printf introduced this %(datefmt)T notation beginning with bash-4.2-alpha. – bishop Mar 27 at 15:40
-1

Here one

secs=378444
echo $(($secs/86400))d $(($(($secs - $secs/86400*86400))/3600))h:$(($(($secs - $secs/86400*86400))%3600/60))m:$(($(($secs - $secs/86400*86400))%60))s

Output:

4d 9h:7m:24s
-2

date --date '@1005454800' gives you Sun Nov 11 00:00:00 EST 2001, which is 1005454800 seconds after the Unix epoch. You can format that with the date +FORMAT option.

  • 7
    That's a date, not a duration, which the question was asking about. – Gilles Dec 17 '11 at 23:02

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.