Reputation: 821
I have a function, inspired by hms
from here, but I wish to extend it to include handling and displaying Days.
I started editing the script but quickly realized I'm out of my depth with handling the logic of hours running into days and visa versa...
Here's what I have so far:
#!/bin/bash
rendertimer(){
# convert seconds to Days, Hours, Minutes, Seconds
# thanks to https://www.shellscript.sh/tips/hms/
local seconds D H M S MM D_TAG H_TAG M_TAG S_TAG
seconds=${1:-0}
S=$((seconds%60))
MM=$((seconds/60)) # total number of minutes
M=$((MM%60))
H=$((MM/60))
D=$((H/24))
# set up "x day(s), x hour(s), x minute(s) and x second(s)" format
[ "$D" -eq "1" ] && D_TAG="day" || D_TAG="days"
[ "$H" -eq "1" ] && H_TAG="hour" || H_TAG="hours"
[ "$M" -eq "1" ] && M_TAG="minute" || M_TAG="minutes"
[ "$S" -eq "1" ] && S_TAG="second" || S_TAG="seconds"
# logic for handling display
[ "$D" -gt "0" ] && printf "%d %s " $D "${D_TAG},"
[ "$H" -gt "0" ] && printf "%d %s " $H "${H_TAG},"
[ "$seconds" -ge "60" ] && printf "%d %s " $M "${M_TAG} and"
printf "%d %s\n" $S "${S_TAG}"
}
duration=${1}
howlong="$(rendertimer $duration)"
echo "That took ${howlong} to run."
349261
seconds: "That took 4 days, 1 hour, 1 minute and 1 second to run."
127932
seconds: "That took 1 day, 11 hours, 32 minutes and 12 seconds to run."
86400
seconds: "That took 1 day to run."
349261
seconds: "That took 4 days, 97 hours, 1 minute and 1 second to run."
127932
seconds: "That took 1 day, 35 hours, 32 minutes and 12 seconds to run."
86400
seconds: "That took 1 day, 24 hours, 0 minutes and 0 seconds to run."
Can someone help me figure this out?
Upvotes: 0
Views: 844
Reputation: 15246
I know this is an old post, but a little more abstraction could make it more concise and flexible, and hopefully the examples of argument parsing, error handling, and usage instructions will come in handy for someone...and it was fun.
Suggestions for improvement welcome.
breakout () {
local secs msg s unit all cnt bad times=() use="
Use: breakout [ [-{a|m}] NUMBER_OF_SECONDS_n.. ]
Breaks NUMBER_OF_SECONDS into weeks, days, hours, minutes seconds.
Only lists seconds with zero values without -a or -m
Dash-options apply only to the next NUMBER_OF_SECONDS
-m reports time units in the 'middle', after a nonzero value
-a reports weeks to seconds (overrides -m if stacked)
"
(( $# )) || { echo "$use" >&2; return; }
declare -A fill sxper=([week]=604800 [day]=86400 [hour]=3600 [minute]=60 [second]=1)
for secs in "$@"
do case $secs in
-[am]) fill[${secs#-}]=true; continue ;;
*[^0-9]*) echo "Invalid argument '$secs'" >&2; fill=(); continue ;;
esac
for unit in week day hour minute second
do all=${sxper[$unit]}
if (( secs >= all ))
then cnt=$((secs/all)); ((cnt>1)) && s=s || s=""; msg+="${msg:+, }$cnt $unit${s:-}"; secs=$((secs%all))
elif [[ -n "${fill[a]:-}" ]]; then msg+="${msg:+, }0 ${unit}s";
elif [[ -n "${fill[m]:-}" ]]; then msg+="${msg:+, 0 ${unit}s}";
fi
done
echo ${msg:-0 seconds}; msg=""; fill=()
done
}
This lets you provide multiple inputs with varying formats, and reasonably sane defaults after an error.
$: breakout
Use: breakout [ [-{a|m}] NUMBER_OF_SECONDS_n.. ]
Breaks NUMBER_OF_SECONDS into weeks, days, hours, minutes seconds.
Only lists seconds with zero values without -a or -m
Dash-options apply only to the next NUMBER_OF_SECONDS
-m reports time units in the 'middle', after a nonzero value
-a reports weeks to seconds (overrides -m if stacked)
$: breakout 86401 -a 86401 -m 86401 -a foo -x 86401 -m 86401
1 day, 1 second
0 weeks, 1 day, 0 hours, 0 minutes, 1 second
1 day, 0 hours, 0 minutes, 1 second
Invalid argument 'foo'
Invalid argument '-x'
1 day, 1 second
1 day, 0 hours, 0 minutes, 1 second
Weeks is probably not useful very often, but it was easy to add since it's a fixed number of seconds. I didn't bother with months and years because they are very seldom going to be needed (OP didn't ask for weeks), and probably aren't worth the extra work to handle the varying lengths.
Upvotes: 1
Reputation: 821
Thanks to @BeardOverflow and @NikolaySidorov, this works great:
#!/bin/bash
rendertimer(){
# convert seconds to Days, Hours, Minutes, Seconds
# thanks to Nikolay Sidorov and https://www.shellscript.sh/tips/hms/
local parts seconds D H M S D_TAG H_TAG M_TAG S_TAG
seconds=${1:-0}
# all days
D=$((seconds / 60 / 60 / 24))
# all hours
H=$((seconds / 60 / 60))
H=$((H % 24))
# all minutes
M=$((seconds / 60))
M=$((M % 60))
# all seconds
S=$((seconds % 60))
# set up "x day(s), x hour(s), x minute(s) and x second(s)" language
[ "$D" -eq "1" ] && D_TAG="day" || D_TAG="days"
[ "$H" -eq "1" ] && H_TAG="hour" || H_TAG="hours"
[ "$M" -eq "1" ] && M_TAG="minute" || M_TAG="minutes"
[ "$S" -eq "1" ] && S_TAG="second" || S_TAG="seconds"
# put parts from above that exist into an array for sentence formatting
parts=()
[ "$D" -gt "0" ] && parts+=("$D $D_TAG")
[ "$H" -gt "0" ] && parts+=("$H $H_TAG")
[ "$M" -gt "0" ] && parts+=("$M $M_TAG")
[ "$S" -gt "0" ] && parts+=("$S $S_TAG")
# construct the sentence
result=""
lengthofparts=${#parts[@]}
for (( currentpart = 0; currentpart < lengthofparts; currentpart++ )); do
result+="${parts[$currentpart]}"
# if current part is not the last portion of the sentence, append a comma
[ $currentpart -ne $((lengthofparts-1)) ] && result+=", "
done
echo "$result"
}
duration=$1
howlong="$(rendertimer "${duration}")"
echo "That took ${howlong} to run."
Upvotes: 1
Reputation: 1070
Replace
H=$((MM/60))
D=$((H/24))
to
H=$((MM/60%24))
D=$((MM/60/24))
So, the days are the quotient of your total minutes and the hours are the modulus of your total minutes.
Also, your second example is wrong. The desire output for 127932 is That took 1 day, 11 hours, 32 minutes and 12 seconds to run
Upvotes: 2