Task

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.
This is detailed below (e.g., "2 hr, 59 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 minute = 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).




 
-------------------- COMPOUND DURATIONS ------------------
 
-- weekParts Int -> [Int]
on weekParts(intSeconds)
    unitParts(intSeconds, [missing value, 7, 24, 60, 60])
end weekParts
 
 
-- localCompoundDuration :: Int -> String
on localCompoundDuration(localNames, intSeconds)
 
    -- [String] -> (Int, String) -> [String]
    script formatted
        on |λ|(lstPair, a)
            set q to item 1 of lstPair
            if q > 0 then
                {(q as string) & space & item 2 of lstPair} & a
            else
                a
            end if
        end |λ|
    end script
 
    intercalate(", ", ¬
        foldr(formatted, [], ¬
            zip(weekParts(intSeconds), localNames)))
end localCompoundDuration
 
------------------ INTEGER DECOMPOSITION -----------------
 
-- unitParts :: Int -> [maybe Int] -> [Int]
on unitParts(intTotal, unitList)
    -- partList :: Record -> Int -> Record
    script partList
        on |λ|(x, a)
            set intRest to remaining of a
 
            if x is not missing value then
                set intMod to intRest mod x
                set d to x
            else
                set intMod to intRest
                set d to 1
            end if
 
            {remaining:(intRest - intMod) div d, parts:{intMod} & parts of a}
        end |λ|
    end script
 
    parts of foldr(partList, ¬
        {remaining:intTotal, parts:[]}, unitList)
end unitParts
 
--------------------------- TEST -------------------------
on run
    script angloNames
        on |λ|(n)
            (n as string) & "     ->    " & ¬
                localCompoundDuration(["wk", "d", "hr", "min", "sec"], n)
        end |λ|
    end script
 
    unlines(map(angloNames, [7259, 86400, 6000000]))
end run
 
 
-------------------- GENERIC FUNCTIONS -------------------
 
-- foldr :: (a -> b -> b) -> b -> [a] -> b
on foldr(f, startValue, xs)
    tell mReturn(f)
        set v to startValue
        set lng to length of xs
        repeat with i from lng to 1 by -1
            set v to |λ|(item i of xs, v, i, xs)
        end repeat
        return v
    end tell
end foldr
 
 
-- intercalate :: String -> [String] -> String
on intercalate(delim, xs)
    set {dlm, my text item delimiters} to ¬
        {my text item delimiters, delim}
    set s to xs as text
    set my text item delimiters to dlm
    s
end intercalate
 
 
-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    -- The list obtained by applying f
    -- to each element of xs.
    tell mReturn(f)
        set lng to length of xs
        set lst to {}
        repeat with i from 1 to lng
            set end of lst to |λ|(item i of xs, i, xs)
        end repeat
        return lst
    end tell
end map
 
 
-- min :: Ord a => a -> a -> a
on min(x, y)
    if y < x then
        y
    else
        x
    end if
end min
 
 
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    -- 2nd class handler function lifted into 1st class script wrapper. 
    if script is class of f then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn
 
 
-- unlines :: [String] -> String
on unlines(xs)
    -- A single string formed by the intercalation
    -- of a list of strings with the newline character.
    set {dlm, my text item delimiters} to ¬
        {my text item delimiters, linefeed}
    set s to xs as text
    set my text item delimiters to dlm
    s
end unlines
 
 
-- zip :: [a] -> [b] -> [(a, b)]
on zip(xs, ys)
    -- A list of step-wise pairs drawn from xs and ys
    -- up to the length of the shorter of those lists.
    set lng to min(length of xs, length of ys)
    set zs to {}
    repeat with i from 1 to lng
        set end of zs to {item i of xs, item i of ys}
    end repeat
    return zs
end zip