Convert seconds to compound duration

Write a function or program which

  • takes a positive integer representing a duration in seconds as input (e.g. 100), and
  • returns a string which shows the same duration decomposed into weeks, days, hours, minutes, and seconds as detailed below (e.g. "1 min, 40 sec").
Task
Convert seconds to compound duration
You are encouraged to solve this task according to the task description, using any language you may know.

Demonstrate that it passes the following three test-cases:

Test Cases

input number output string
7259 2 hr, 59 sec
86400 1 d
6000000 9 wk, 6 d, 10 hr, 40 min

Details

  • The following five units should be used:
    unit suffix used in output conversion
    week wk 1 week = 7 days
    day d 1 day = 24 hours
    hour hr 1 hour = 60 minutes
    minute min 1 minutes = 60 seconds
    second sec
  • However, only include quantities with non-zero values in the output (e.g. return "1 d" and not "0 wk, 1 d, 0 hr, 0 min, 0 sec").
  • Give larger units precedence over smaller ones as much as possible (e.g. return 2 min, 10 sec and not 1 min, 70 sec or 130 sec)
  • Mimic the formatting shown in the test-cases (quantities sorted from largest unit to smallest and separated by comma+space; value and unit of each quantity separated by space).

AWK

<lang AWK>

  1. syntax: GAWK -f CONVERT_SECONDS_TO_COMPOUND_DURATION.AWK

BEGIN {

   n = split("7259 86400 6000000 0 1 60 3600 604799 604800 694861",arr," ")
   for (i=1; i<=n; i++) {
     printf("%9s %s\n",arr[i],howlong(arr[i]))
   }
   exit(0)

} function howlong(seconds, n_day,n_hour,n_min,n_sec,n_week,str,x) {

   if (seconds >= (x = 60*60*24*7)) {
     n_week = int(seconds / x)
     seconds = seconds % x
   }
   if (seconds >= (x = 60*60*24)) {
     n_day = int(seconds / x)
     seconds = seconds % x
   }
   if (seconds >= (x = 60*60)) {
     n_hour = int(seconds / x)
     seconds = seconds % x
   }
   if (seconds >= (x = 60)) {
     n_min = int(seconds / x)
     seconds = seconds % x
   }
   n_sec = int(seconds)
   str = (n_week > 0) ? (str n_week " wk, ") : str
   str = (n_day > 0) ? (str n_day " d, ") : str
   str = (n_hour > 0) ? (str n_hour " hr, ") : str
   str = (n_min > 0) ? (str n_min " min, ") : str
   str = (n_sec > 0) ? (str n_sec " sec") : str
   sub(/, $/,"",str)
   return(str)

} </lang>

Output:

     7259 2 hr, 59 sec
    86400 1 d
  6000000 9 wk, 6 d, 10 hr, 40 min
        0
        1 1 sec
       60 1 min
     3600 1 hr
   604799 6 d, 23 hr, 59 min, 59 sec
   604800 1 wk
   694861 1 wk, 1 d, 1 hr, 1 min, 1 sec

Batch File

<lang dos>@echo off

The Main Thing...

for %%d in (7259 86400 6000000) do call :duration %%d exit/b 0

/The Main Thing.
The Function...
duration

set output= set /a "wk=%1/604800,rem=%1%%604800" if %wk% neq 0 set "output= %wk% wk,"

set /a "d=%rem%/86400,rem=%rem%%%86400" if %d% neq 0 set "output=%output% %d% d,"

set /a "hr=%rem%/3600,rem=%rem%%%3600" if %hr% neq 0 set "output=%output% %hr% hr,"

set /a "min=%rem%/60,rem=%rem%%%60" if %min% neq 0 set "output=%output% %min% min,"

if %rem% neq 0 set "output=%output% %rem% sec,"

if %1 gtr 0 echo %1 sec = %output:~1,-1% goto :EOF

/The Function.</lang>
Output:
7259 sec = 2 hr, 59 sec
86400 sec = 1 d
6000000 sec = 9 wk, 6 d, 10 hr, 40 min

Haskell

<lang haskell>import Control.Monad (forM_) import Data.List (intercalate, mapAccumR) import System.Environment (getArgs) import Text.Printf (printf) import Text.Read (readMaybe)

reduceBy :: Integral a => a -> [a] -> [a] n `reduceBy` xs = n' : ys where (n', ys) = mapAccumR quotRem n xs

-- Duration/label pairs. durLabs :: [(Integer, String)] durLabs = [(undefined, "wk"), (7, "d"), (24, "hr"), (60, "min"), (60, "sec")]

-- Time broken down into non-zero durations and their labels. compdurs :: Integer -> [(Integer, String)] compdurs t = let ds = t `reduceBy` (map fst $ tail durLabs)

            in filter ((/=0) . fst) $ zip ds (map snd durLabs)

-- Compound duration of t seconds. The argument is assumed to be positive. compoundDuration :: Integer -> String compoundDuration = intercalate ", " . map (uncurry $ printf "%d %s") . compdurs

main :: IO () main = do

 args <- getArgs
 forM_ args $ \arg -> case readMaybe arg of
   Just n  -> printf "%7d seconds = %s\n" n (compoundDuration n)
   Nothing -> putStrLn $ "Invalid number of seconds: " ++ arg</lang>
Output:
   7259 seconds = 2 hr, 59 sec
  86400 seconds = 1 d
6000000 seconds = 9 wk, 6 d, 10 hr, 40 min

J

Implementation:

<lang J>fmtsecs=:3 :0

 seq=. 0 7 24 60 60 #: y
 }: ;: inv,(0~:seq)#(8!:0 seq),. <;.2'wk,d,hr,min,sec,'

)</lang>

Task examples:

<lang J> fmtsecs 7259 2 hr, 59 sec

  fmtsecs 86400

1 d

  fmtsecs 6000000

9 wk, 6 d, 10 hr, 40 min</lang>

jq

Works with: jq version 1.4

<lang jq>def seconds_to_time_string:

 def nonzero(text): floor | if . > 0 then "\(.) \(text)" else empty end;
 if . == 0 then "0 sec"
 else
 [(./60/60/24/7    | nonzero("wk")),
  (./60/60/24 % 7  | nonzero("d")),
  (./60/60    % 24 | nonzero("hr")),
  (./60       % 60 | nonzero("min")),
  (.          % 60 | nonzero("sec"))]
 | join(", ")
 end;</lang>

Examples': <lang jq>0, 7259, 86400, 6000000 | "\(.): \(seconds_to_time_string)"</lang>

Output:

<lang sh>$ jq -r -n -f Convert_seconds_to_compound_duration.jq 0: 0 sec 7259: 2 hr, 59 sec 86400: 1 d 6000000: 9 wk, 6 d, 10 hr, 40 min</lang>

Perl

<lang perl>sub compound_duration {

   my $sec = shift;
   no warnings 'numeric';
   
   return join ', ', grep { $_ > 0 }
       int($sec/60/60/24/7)    . " wk",
       int($sec/60/60/24) % 7  . " d",
       int($sec/60/60)    % 24 . " hr",
       int($sec/60)       % 60 . " min",
       int($sec)          % 60 . " sec";

}</lang>

Demonstration: <lang perl>for (7259, 86400, 6000000) {

   printf "%7d sec  =  %s\n", $_, compound_duration($_)

}</lang>

Output:
   7259 sec  =  2 hr, 59 sec
  86400 sec  =  1 d
6000000 sec  =  9 wk, 6 d, 10 hr, 40 min

Perl 6

The built-in polymod method (which is a generalization of the divmod function known from other languages), is a perfect match for a task like this:

<lang perl6>sub compound-duration ($seconds) {

   ($seconds.polymod(60, 60, 24, 7) Z <sec min hr d wk>)\
   .grep(*[0]).reverse.join(", ")

}</lang>

Demonstration: <lang perl6>for 7259, 86400, 6000000 {

   say "{.fmt: '%7d'} sec  =  {compound-duration $_}";

}</lang>

Output:
   7259 sec  =  2 hr, 59 sec
  86400 sec  =  1 d
6000000 sec  =  9 wk, 6 d, 10 hr, 40 min

Python

Python: Procedural

<lang python>>>> def duration(seconds): t= [] for dm in (60, 60, 24, 7): seconds, m = divmod(seconds, dm) t.append(m) t.append(seconds) return ', '.join('%d %s' % (num, unit) for num, unit in zip(t[::-1], 'wk d hr min sec'.split()) if num)

>>> for seconds in [7259, 86400, 6000000]: print("%7d sec = %s" % (seconds, duration(seconds)))


  7259 sec = 2 hr, 59 sec
 86400 sec = 1 d

6000000 sec = 9 wk, 6 d, 10 hr, 40 min >>> </lang>

Python: Functional

<lang python>>>> def duration(seconds, _maxweeks=99999999999):

   return ', '.join('%d %s' % (num, unit)

for num, unit in zip([(seconds // d) % m for d, m in ((604800, _maxweeks),

                                                       (86400, 7), (3600, 24), 
                                                       (60, 60), (1, 60))],

['wk', 'd', 'hr', 'min', 'sec']) if num)

>>> for seconds in [7259, 86400, 6000000]: print("%7d sec = %s" % (seconds, duration(seconds)))


  7259 sec = 2 hr, 59 sec
 86400 sec = 1 d

6000000 sec = 9 wk, 6 d, 10 hr, 40 min >>> </lang>

REXX

version 1

<lang rexx>/* REXX ---------------------------------------------------------------

  • Format seconds into a time string
  • --------------------------------------------------------------------*/

Call test 7259 ,'2 hr, 59 sec' Call test 86400 ,'1 d' Call test 6000000 ,'9 wk, 6 d, 10 hr, 40 min' Exit

test:

 Parse arg secs,xres
 res=sec2ct(secs)
 Say res
 If res<>xres Then Say '**ERROR**'
 Return

sec2ct: Parse Arg s /* m=s%60; s=s//60 h=m%60; m=m//60 d=h%24; h=h//24 w=d%7; d=d//7

  • /

Parse Value split(s,60) with m s Parse Value split(m,60) with h m Parse Value split(h,24) with d h Parse Value split(d, 7) with w d ol= If w>0 Then ol=ol w 'wk,' If d>0 Then ol=ol d 'd,' If h>0 Then ol=ol h 'hr,' If m>0 Then ol=ol m 'min,' If s>0 Then ol=ol s 'sec' ol=strip(ol) ol=strip(ol,,',') Return ol

split: Procedure

 Parse Arg what,how
 a=what%how
 b=what//how
 Return a b</lang>
Output:
2 hr, 59 sec
1 d
9 wk, 6 d, 10 hr, 40 min

version 2

This REXX version can also handle fractional (seconds) as well as values of zero (time units). <lang rexx>/*rexx program demonstrates how to convert a number of seconds to bigger units*/ parse arg @; if @= then @=7259 86400 6000000 /*Not specified? Use default*/

      do j=1  for words(@);           /* [↓]  process each number in the list*/
      call convSec word(@,j)          /*convert a number to bigger time units*/
      end   /*j*/

exit /*stick a fork in it, we're all done. */ /*─────────────────────────────────CONVSEC subroutine─────────────────────────*/ convSec: parse arg x /*obtain a number from the argument. */ w=timeU(60*60*24*7, 'wk' ) /*obtain number of weeks (if any). */ d=timeU(60*60*24 , 'd' ) /* " " " days " " */ h=timeU(60*60 , 'hr' ) /* " " " hours " " */ m=timeU(60 , 'min' ) /* " " " minutes " " */ s=timeU(1 , 'sec' ) /* " " " seconds " " */ if x\==0 then s=word(s 0,1)+x 'sec' /*handle fractional (residual) seconds.*/ z=strip(space(w d h m s),,','); if z== then z=0 'sec' /*handle zero sec.*/ say right(arg(1), 20) 'seconds: ' z return /*─────────────────────────────────TIMEU subroutine───────────────────────────*/ timeU: parse arg u,$; _=x%u; if _==0 then return ; x=x-_*u; return _ $','</lang> output when using the default inputs:

                7259 seconds:  2 hr, 59 sec
               86400 seconds:  1 d
             6000000 seconds:  9 wk, 6 d, 10 hr, 40 min

output when using the inputs:   1800.7   123.50   123.00   0.00

              1800.7 seconds:  30 min, 0.7 sec
              123.50 seconds:  2 min, 3.50 sec
              123.00 seconds:  2 min, 3 sec
                0.00 seconds:  0 sec

Tcl

The data-driven procedure below can be customised to use different breakpoints, simply by editing the dictionary.

<lang Tcl>proc sec2str {i} {

   set factors {
       sec 60
       min 60
       hr  24
       d   7
       wk  Inf
   }
   set result ""
   foreach {label max} $factors {
       if {$i >= $max} {
           set r [expr {$i % $max}]
           set i [expr {$i / $max}]
           if {$r} {
               lappend result "$r $label"
           }
       } else {
           if {$i > 0} {
               lappend result "$i $label"
           }
           break
       }
   }
   join [lreverse $result] ", "

}

proc check {cmd res} {

   set r [uplevel 1 $cmd]
   if {$r eq $res} {
       puts "Ok! $cmd \t = $res"
   } else {
       puts "ERROR: $cmd = $r \t expected $res"
   }

}

check {sec2str 7259} {2 hr, 59 sec} check {sec2str 86400} {1 d} check {sec2str 6000000} {9 wk, 6 d, 10 hr, 40 min}</lang>

Output:
Ok! sec2str 7259         = 2 hr, 59 sec
Ok! sec2str 86400        = 1 d
Ok! sec2str 6000000      = 9 wk, 6 d, 10 hr, 40 min

zkl

<lang zkl>fcn toWDHMS(sec){ //-->(wk,d,h,m,s)

  r,b:=List(),0;
  foreach u in (T(60, 60, 24, 7)){
     sec,b=sec.divr(u);   // aka divmod
     r.append(b);
  }
  r.append(sec).reverse()

}</lang> <lang zkl>units:=T(" wk"," d"," hr"," min"," sec"); foreach s in (T(7259,86400,6000000)){

  toWDHMS(s).zip(units).pump(List,fcn([(t,u)]){ t and String(t,u) or "" })
  .filter().concat(", ").println();

}</lang>

Output:
2 hr, 59 sec
1 d
9 wk, 6 d, 10 hr, 40 min