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




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