Convert seconds to compound duration

From Rosetta Code
Revision as of 18:43, 28 June 2015 by rosettacode>Globules (Haskell version.)
Convert seconds to compound duration is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

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").

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) import System.Environment (getArgs) import Text.Printf (printf) import Text.Read (readMaybe)

reduceBy :: Integral a => a -> [a] -> [a] n `reduceBy` [] = [n] n `reduceBy` (d:ds) = let (q, r) = n `quotRem` d in q : r `reduceBy` ds

-- Duration/label pairs. durLabs :: [(Integer, String)] durLabs = [(1, "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` scanr1 (*) (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