Convert seconds to compound duration

From Rosetta Code
Revision as of 06:41, 21 July 2015 by Tim-brown (talk | contribs) (→‎{{header|Racket}}: implementation added)
Task
Convert seconds to compound duration
You are encouraged to solve this task according to the task description, using any language you may know.

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

Ada

<lang Ada>with Ada.Text_IO;

procedure Convert is

  type Time is range 0 .. 10_000*356*20*60*60; -- at most 10_000 years
  subtype Valid_Duration is Time range 1  .. 10_000*356*20*60*60;
  type Units is (WK, D, HR, MIN, SEC);
     
  package IO renames Ada.Text_IO;
  
  Divide_By: constant array(Units) of Time := (1_000*53, 7, 24, 60, 60);
  Split: array(Units) of Time;
  No_Comma: Units;
  X: Time;
  
  Test_Cases: array(Positive range <>) of Valid_Duration :=
    (6, 60, 3659, 7_259, 86_400, 6_000_000, 6_001_200, 6_001_230, 600_000_000);
  

begin

 for Test_Case of Test_Cases loop
    IO.Put(Time'Image(Test_Case) & " SECONDS =");
    X := Test_Case;
    -- split X up into weeks, days, ..., seconds
    No_Comma := Units'First;
    for Unit in reverse Units loop -- Unit = SEC, ..., WK (in that order)

Split(Unit) := X mod Divide_By(Unit); X := X / Divide_By(Unit); if Unit > No_Comma and Split(Unit)>0 then No_Comma := Unit; end if;

    end loop;
    -- ouput weeks, days, ..., seconds
    for Unit in Units loop -- Unit =  WK, .., SEC (in that order)

if Split(Unit) > 0 then IO.Put(Time'Image(Split(Unit)) & " " & Units'Image(Unit) & (if No_Comma > Unit then "," else "")); end if;

    end loop;
 IO.New_Line;
 end loop;

end Convert;</lang>

Output:
 6 SECONDS = 6 SEC
 60 SECONDS = 1 MIN
 3659 SECONDS = 1 HR, 59 SEC
 7259 SECONDS = 2 HR, 59 SEC
 86400 SECONDS = 1 D
 6000000 SECONDS = 9 WK, 6 D, 10 HR, 40 MIN
 6001200 SECONDS = 9 WK, 6 D, 11 HR
 6001230 SECONDS = 9 WK, 6 D, 11 HR, 30 SEC
 600000000 SECONDS = 992 WK, 10 HR, 40 MIN

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


C

<lang c>

  1. include <inttypes.h> /* requires c99 */
  2. include <stdbool.h> /* requires c99 */
  3. include <stdio.h>
  4. include <stdlib.h>
  1. define N_EL 5

uintmax_t sec_to_week(uintmax_t); uintmax_t sec_to_day(uintmax_t); uintmax_t sec_to_hour(uintmax_t); uintmax_t sec_to_min(uintmax_t);

uintmax_t week_to_sec(uintmax_t); uintmax_t day_to_sec(uintmax_t); uintmax_t hour_to_sec(uintmax_t); uintmax_t min_to_sec(uintmax_t);

char *format_sec(uintmax_t);

   /* the primary function */


int main(int argc, char *argv[]) {

   uintmax_t input;
   char *a;
   
   if(argc<2) {
       printf("usage: %s #seconds\n", argv[0]);
       return 1;
   }
   input = strtoumax(argv[1],(void *)0, 10 /*base 10*/);
   if(input<1) {
       printf("Bad input: %s\n", argv[1]);
       printf("usage: %s #seconds\n", argv[0]);
       return 1;
   }
   printf("Number entered: %" PRIuMAX "\n", input);
   a = format_sec(input);
   printf(a);
   free(a);
   
   return 0;

}

/* note: must free memory

* after using this function */

char *format_sec(uintmax_t input) {

   int i;
   bool first;
   uintmax_t weeks, days, hours, mins; 
   /*seconds kept in input*/
   
   char *retval;
   FILE *stream;
   size_t size;
   uintmax_t *traverse[N_EL]={&weeks,&days,
           &hours,&mins,&input};
   char *labels[N_EL]={"wk","d","hr","min","sec"};
   weeks = sec_to_week(input);
   input = input - week_to_sec(weeks);
   days = sec_to_day(input);
   input = input - day_to_sec(days);
   hours = sec_to_hour(input);
   input = input - hour_to_sec(hours);
   mins = sec_to_min(input);
   input = input - min_to_sec(mins); 
   /* input now has the remaining seconds */
   /* open stream */
   stream = open_memstream(&retval,&size);
   if(stream == 0) {
       fprintf(stderr,"Unable to allocate memory");
       return 0;
   }
   /* populate stream */
   first = true;
   for(i=0;i<N_EL;i++) {
       if ( *(traverse[i]) != 0 ) {
           if(!first) {
               fprintf(stream,", %" PRIuMAX " %s",
                       *(traverse[i]), labels[i]);
           } else {
               fprintf(stream,"%" PRIuMAX " %s",
                       *(traverse[i]), labels[i]);
           }
           fflush(stream);
           first=false;
       }
   }
   fprintf(stream,"\n");
   fclose(stream);
   return retval;

}

uintmax_t sec_to_week(uintmax_t seconds) {

   return sec_to_day(seconds)/7;

}

uintmax_t sec_to_day(uintmax_t seconds) {

   return sec_to_hour(seconds)/24;

}

uintmax_t sec_to_hour(uintmax_t seconds) {

   return sec_to_min(seconds)/60;

}

uintmax_t sec_to_min(uintmax_t seconds) {

   return seconds/60;

}

uintmax_t week_to_sec(uintmax_t weeks) {

   return day_to_sec(weeks*7);

}

uintmax_t day_to_sec(uintmax_t days) {

   return hour_to_sec(days*24);

}

uintmax_t hour_to_sec(uintmax_t hours) {

   return min_to_sec(hours*60);

}

uintmax_t min_to_sec(uintmax_t minutes) {

   return minutes*60;

} </lang>

Output:
Number entered: 7259
2 hr, 59 sec 

Number entered: 86400
1 d

Number entered: 6000000
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>

Racket

<lang racket>#lang racket/base (require racket/string

        racket/list)

(define (seconds->compound-durations s)

 (define-values (w d.h.m.s)
   (for/fold ((prev-q s) (rs (list))) ((Q (in-list (list 60 60 24 7))))
     (define-values (q r) (quotient/remainder prev-q Q))
     (values q (cons r rs))))
 (cons w d.h.m.s))

(define (maybe-suffix v n)

 (and (positive? v)
      (format "~a ~a" v n)))

(define (seconds->compound-duration-string s)

 (string-join (filter-map maybe-suffix
                          (seconds->compound-durations s)
                          '("wk" "d" "hr" "min" "sec"))
              ", "))

(module+ test

 (require rackunit)
 (check-equal? (seconds->compound-durations 7259)    (list 0 0  2  0 59))
 (check-equal? (seconds->compound-durations 86400)   (list 0 1  0  0  0))
 (check-equal? (seconds->compound-durations 6000000) (list 9 6 10 40  0))
 
 (check-equal? (seconds->compound-duration-string 7259)    "2 hr, 59 sec")
 (check-equal? (seconds->compound-duration-string 86400)   "1 d")
 (check-equal? (seconds->compound-duration-string 6000000) "9 wk, 6 d, 10 hr, 40 min"))
Tim Brown 2015-07-21</lang>
Output:

All tests pass... there is no output.

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' Call test 123.50 ,'2 min, 3.5 sec' Call test 123.00 ,'2 min, 3 sec' Call test 0.00 ,'0 sec' 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

  • /

If s=0 Then Return '0 sec' 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/1) '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
2 min, 3.5 sec
2 min, 3 sec
0 sec        

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

VBScript

<lang vb> Function compound_duration(n) Do Until n = 0 If n >= 604800 Then wk = Int(n/604800) n = n-(604800*wk) compound_duration = compound_duration & wk & " wk" End If If n >= 86400 Then d = Int(n/86400) n = n-(86400*d) If wk > 0 Then compound_duration = compound_duration & ", " End If compound_duration = compound_duration & d & " d" End If If n >= 3600 Then hr = Int(n/3600) n = n-(3600*hr) If d > 0 Then compound_duration = compound_duration & ", " End If compound_duration = compound_duration & hr & " hr" End If If n >= 60 Then min = Int(n/60) n = n-(60*min) If hr > 0 Then compound_duration = compound_duration & ", " End If compound_duration = compound_duration & min & " min" End If If n > 0 Then If min > 0 Then compound_duration = compound_duration & ", " End If compound_duration = compound_duration & ", " & n & " sec" n = 0 End If Loop End Function

'validating the function WScript.StdOut.WriteLine compound_duration(7259) WScript.StdOut.WriteLine compound_duration(86400) WScript.StdOut.WriteLine compound_duration(6000000) </lang>

Output:
2 hr, 59 sec
1 d
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> Or, if you like to be concise: <lang zkl>fcn toWDHMS(sec){ //-->(wk,d,h,m,s)

  T(60,60,24,7).reduce(fcn(n,u,r){ n,u=n.divr(u); r.append(u); n },
     sec,r:=List()):r.append(_).reverse();

}</lang> were the ":" op takes the left result and stuffs it into the "_" position. <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