How can I convert seconds to hours, minutes and seconds?
show_time() {
?????
}
show_time 36 # 00:00:36
show_time 1036 # 00:17:26
show_time 91925 # 25:32:05
How can I convert seconds to hours, minutes and seconds?
show_time() {
?????
}
show_time 36 # 00:00:36
show_time 1036 # 00:17:26
show_time 91925 # 25:32:05
I use the following function myself:
function show_time () {
num=$1
min=0
hour=0
day=0
if((num>59));then
((sec=num%60))
((num=num/60))
if((num>59));then
((min=num%60))
((num=num/60))
if((num>23));then
((hour=num%24))
((day=num/24))
else
((hour=num))
fi
else
((min=num))
fi
else
((sec=num))
fi
echo "$day"d "$hour"h "$min"m "$sec"s
}
Note it counts days as well. Also, it shows a different result for your last number.
echo Elapsed time: $(date -ud "@$elapsed" +'$((%s/3600/24)) days %H hr %M min %S sec')"
to work on both (even tried using gdate instead, without success) –
Rozek Use date, converted to UTC:
$ date -d@36 -u +%H:%M:%S
00:00:36
$ date -d@1036 -u +%H:%M:%S
00:17:16
$ date -d@12345 -u +%H:%M:%S
03:25:45
The limitation is the hours will loop at 23, but that doesn't matter for most use cases where you want a one-liner.
On macOS, run brew install coreutils
and replace date
with gdate
date
, and is not portable. –
Survivor coreutils
on MacOS, you can use the ootb date
: https://mcmap.net/q/95395/-convert-seconds-to-hours-minutes-seconds –
Votyak date -u +"$(( ${seconds} / 3600 )):%M:%S" -d "@${seconds}"
–
Jareb #!/bin/sh
convertsecs() {
((h=${1}/3600))
((m=(${1}%3600)/60))
((s=${1}%60))
printf "%02d:%02d:%02d\n" $h $m $s
}
TIME1="36"
TIME2="1036"
TIME3="91925"
echo $(convertsecs $TIME1)
echo $(convertsecs $TIME2)
echo $(convertsecs $TIME3)
For float seconds:
convertsecs() {
h=$(bc <<< "${1}/3600")
m=$(bc <<< "(${1}%3600)/60")
s=$(bc <<< "${1}%60")
printf "%02d:%02d:%05.2f\n" $h $m $s
}
bash: bc: command not found
–
Marroquin ((...))
is ksh syntax indeed (also supported by zsh/bash). bc
however is a non-optional standard POSIX command. <<<
is zsh syntax though (also supported by bash and a few other shells these days, but not standard sh
syntax). Leaving the parameter expansions unquoted implies zsh syntax. In other shells, they're subject to split+glob. But even in zsh the unquoted $(...)
would still be subject to splitting. The support of %f
is optional in POSIX printf
. –
Marivaux $ secs=236521
$ printf '%dh:%dm:%ds\n' $((secs/3600)) $((secs%3600/60)) $((secs%60))
65h:42m:1s
$ secs=236521
$ printf '%02dh:%02dm:%02ds\n' $((secs/3600)) $((secs%3600/60)) $((secs%60))
65h:42m:01s
$ secs=236521
$ printf '%dd:%dh:%dm:%ds\n' $((secs/86400)) $((secs%86400/3600)) $((secs%3600/60)) \
$((secs%60))
2d:17h:42m:1s
$ secs=21218.6474912
$ printf '%02dh:%02dm:%02fs\n' $(echo -e "$secs/3600\n$secs%3600/60\n$secs%60"| bc)
05h:53m:38.647491s
Based on https://mcmap.net/q/95395/-convert-seconds-to-hours-minutes-seconds but edit got rejected.
The simplest way I know of:
secs=100000
printf '%dh:%dm:%ds\n' $((secs/3600)) $((secs%3600/60)) $((secs%60))
Note - if you want days then just add other unit and divide by 86400.
0
s: use %02d
instead of %d
. –
Guerrilla printf '%dd %dh:%dm:%ds\n' $(($secs/86400)) $(($secs%86400/3600)) $(($secs%3600/60)) $(($secs%60))
–
Sheepdog I use the following function myself:
function show_time () {
num=$1
min=0
hour=0
day=0
if((num>59));then
((sec=num%60))
((num=num/60))
if((num>59));then
((min=num%60))
((num=num/60))
if((num>23));then
((hour=num%24))
((day=num/24))
else
((hour=num))
fi
else
((min=num))
fi
else
((sec=num))
fi
echo "$day"d "$hour"h "$min"m "$sec"s
}
Note it counts days as well. Also, it shows a different result for your last number.
echo Elapsed time: $(date -ud "@$elapsed" +'$((%s/3600/24)) days %H hr %M min %S sec')"
to work on both (even tried using gdate instead, without success) –
Rozek For us lazy people: ready-made script available at https://github.com/k0smik0/FaCRI/blob/master/fbcmd/bin/displaytime :
#!/bin/bash
function displaytime {
local T=$1
local D=$((T/60/60/24))
local H=$((T/60/60%24))
local M=$((T/60%60))
local S=$((T%60))
[[ $D > 0 ]] && printf '%d days ' $D
[[ $H > 0 ]] && printf '%d hours ' $H
[[ $M > 0 ]] && printf '%d minutes ' $M
[[ $D > 0 || $H > 0 || $M > 0 ]] && printf 'and '
printf '%d seconds\n' $S
}
displaytime $1
Basically just another spin on the other solutions, but has the added bonus of suppressing empty time units (f.e. 10 seconds
instead of 0 hours 0 minutes 10 seconds
). Couldn't quite track down the original source of the function, occurs in multiple git repos..
[[...]]
. Replace it with [...]
, and it's completely POSIX. –
Clipboard Using dc
:
$ echo '12345.678' | dc -e '?1~r60~r60~r[[0]P]szn[:]ndZ2>zn[:]ndZ2>zn[[.]n]sad0=ap'
3:25:45.678
The expression ?1~r60~r60~rn[:]nn[:]nn[[.]n]sad0=ap
does the following:
? read a line from stdin
1 push one
~ pop two values, divide, push the quotient followed by the remainder
r reverse the top two values on the stack
60 push sixty
~ pop two values, divide, push the quotient followed by the remainder
r reverse the top two values on the stack
60 push sixty
~ pop two values, divide, push the quotient followed by the remainder
r reverse the top two values on the stack
[ interpret everything until the closing ] as a string
[0] push the literal string '0' to the stack
n pop the top value from the stack and print it with no newline
] end of string, push the whole thing to the stack
sz pop the top value (the string above) and store it in register z
n pop the top value from the stack and print it with no newline
[:] push the literal string ':' to the stack
n pop the top value from the stack and print it with no newline
d duplicate the top value on the stack
Z pop the top value from the stack and push the number of digits it has
2 push two
>z pop the top two values and executes register z if the original top-of-stack is greater
n pop the top value from the stack and print it with no newline
[:] push the literal string ':' to the stack
n pop the top value from the stack and print it with no newline
d duplicate the top value on the stack
Z pop the top value from the stack and push the number of digits it has
2 push two
>z pop the top two values and executes register z if the original top-of-stack is greater
n pop the top value from the stack and print it with no newline
[ interpret everything until the closing ] as a string
[.] push the literal string '.' to the stack
n pop the top value from the stack and print it with no newline
] end of string, push the whole thing to the stack
sa pop the top value (the string above) and store it in register a
d duplicate the top value on the stack
0 push zero
=a pop two values and execute register a if they are equal
p pop the top value and print it with a newline
An example execution with the stack state after each operation:
: <empty stack>
? : 12345.678
1 : 1, 12345.678
~ : .678, 12345
r : 12345, .678 # stack is now seconds, fractional seconds
60 : 60, 12345, .678
~ : 45, 205, .678
r : 205, 45, .678 # stack is now minutes, seconds, fractional seconds
60 : 60, 205, 45, .678
~ : 25, 3, 45, .678
r : 3, 25, 45, .678 # stack is now hours, minutes, seconds, fractional seconds
[[0]n] : [0]n, 3, 25, 45, .678
sz : 3, 25, 45, .678 # '[0]n' stored in register z
n : 25, 45, .678 # accumulated stdout: '3'
[:] : :, 25, 45, .678
n : 25, 45, .678 # accumulated stdout: '3:'
d : 25, 25, 45, .678
Z : 2, 25, 45, .678
2 : 2, 2, 25, 45, .678
>z : 25, 45, .678 # not greater, so register z is not executed
n : 45, .678 # accumulated stdout: '3:25'
[:] : :, 45, .678
n : 45, .678 # accumulated stdout: '3:25:'
d : 45, 45, .678
Z : 2, 45, 45, .678
2 : 2, 2, 45, .678
>z : 45, .678 # not greater, so register z is not executed
n : .678 # accumulated stdout: '3:25:45'
[[.]n] : [.]n, .678
sa : .678 # '[.]n' stored to register a
d : .678, .678
0 : 0, .678, .678
=a : .678 # not equal, so register a not executed
p : <empty stack> # accumulated stdout: '3:25:45.678\n'
In the case of 0 fractional seconds:
: 3, 25, 45, 0 # starting just before we begin to print
n : 25, 45, .678 # accumulated stdout: '3'
[:] : :, 25, 45, .678
n : 25, 45, .678 # accumulated stdout: '3:'
d : 25, 25, 45, .678
Z : 2, 25, 45, .678
2 : 2, 2, 25, 45, .678
>z : 25, 45, .678 # not greater, so register z is not executed
n : 45, .678 # accumulated stdout: '3:25'
[:] : :, 45, .678
n : 45, .678 # accumulated stdout: '3:25:'
d : 45, 45, .678
Z : 2, 45, 45, .678
2 : 2, 2, 45, .678
>z : 45, .678 # not greater, so register z is not executed
n : .678 # accumulated stdout: '3:25:45'
[[.]n] : [.]n, 0
sa : 0 # '[.]n' stored to register a
d : 0, 0
0 : 0, 0, 0
=a : 0 # equal, so register a executed
[.] : ., 0
n : 0 # accumulated stdout: '3:35:45.'
p : <empty stack> # accumulated stdout: '3:25:45.0\n'
In case of a minutes value less than 10:
: 3, 9, 45, 0 # starting just before we begin to print
n : 9, 45, .678 # accumulated stdout: '3'
[:] : :, 9, 45, .678
n : 9, 45, .678 # accumulated stdout: '3:'
d : 9, 9, 45, .678
Z : 1, 9, 45, .678
2 : 2, 1, 9, 45, .678
>z : 9, 45, .678 # greater, so register z is executed
[0] : 0, 9, 45, .678
n : 9, 45, .678 # accumulated stdout: '3:0'
n : 9, .678 # accumulated stdout: '3:09'
# ...and continues as above
EDIT: this had a bug where strings like 7:7:34.123 could be printed. I have modified it to print a leading zero if necessary.
EDIT 2: I realized that the handling of fractional seconds is redundant, and this simplified dc
expression performs almost the same job:
$ echo '12345.678' | dc -e '?60~r60~r[[0]P]szn[:]ndZ2>zn[:]ndZ2>zp'
3:25:45.678
$ echo '12345.0' | dc -e '?60~r60~r[[0]P]szn[:]ndZ2>zn[:]ndZ2>zp'
3:25:45.0
$ echo '12345' | dc -e '?60~r60~r[[0]P]szn[:]ndZ2>zn[:]ndZ2>zp'
3:25:45
The downside of this is that different but equal inputs produce different outputs; any number of zeroes after the decimal point will be echoed verbatim in the output because of the fixed-point nature of dc
.
echo 12345.678 | dc -e '? 1~r 60~r 60~r f' | paste -s | awk '{printf "%02d:%02d:%02d%s\n", $1, $2, $3, $4}'
–
Vespine seq 12345 12349
–
Verney []
and then execute repeatedly by having it call itself at the end. I could imagine there's a limit to the number of nested macros, but maybe using c
somewhere in there (as shown here www2.latech.edu/~acm/helloworld/dc1.html) would solve that. If you write it, I'd love to see it posted here! –
Immorality A MacOS-specific answer which is using the OOTB /bin/date
and does not require the GNU version of date
:
# convert 195 seconds to MM:SS format, i.e. 03:15
/bin/date -ju -f "%s" 195 "+%M:%S"
## OUTPUT: 03:15
If you also want to have hours:
/bin/date -ju -f "%s" 3600 "+%H:%M:%S"
# OUTPUT: 01:00:00
NOTE: If you want to deal with hours then
-u
is required as it's forcing UTC time and without it you'll get wrong output unless you live in the UTC time zone:
-u Display or set the date in UTC (Coordinated Universal) time.
For an explanation why -u
is needed see this.
date -ur 3600 +%T
. The -r
tells it to read the input as seconds since the epoch and not set the system data (essentially equivalent to -j -f "%s"
), and the %T
format is short for %H:%M:%S
. This works on macOS and at least NetBSD, but not with GNU's date
command (i.e. on Linux), where the -r
option means something completely different. –
Mikemikel "+%H:%M:%S"
as the format then anything over one day will not fit and you'll get a rollover effect. See man date
on mac and notice [+output_fmt]
at the end. What you put there will depend on what you expect to get, for example /bin/date -ju -f "%s" 86400 "+%d,%H:%M:%S"
; date
is not really meant to work with intervals, so the syntax above is bordering on abuse; if you need more flexibility/granularity better resort to python/ruby/js or whatever you are more comfortable with. –
Votyak Straight forward through awk
:
echo $SECONDS | awk '{printf "%d:%02d:%02d", $1/3600, ($1/60)%60, $1%60}'
All above is for bash, disregarding there "#!/bin/sh" without any bashism will be:
convertsecs() {
h=`expr $1 / 3600`
m=`expr $1 % 3600 / 60`
s=`expr $1 % 60`
printf "%02d:%02d:%02d\n" $h $m $s
}
expr $1 / 3600
with $(( $1 / 3600 ))
, for example. This is more efficient, as it does not require spawning a new process for each call to expr
. –
Hebetic t=12345;printf %02d:%02d:%02d\\n $((t/3600)) $((t%3600/60)) $((t%60)) # POSIX
echo 12345|awk '{printf "%02d:%02d:%02d",$0/3600,$0%3600/60,$0%60}' # POSIX awk
date -d @12345 +%T # GNU date
date -r 12345 +%T # OS X's date
If others were searching for how to do the reverse:
IFS=: read h m s<<<03:25:45;echo $((h*3600+m*60+s)) # POSIX
echo 03:25:45|awk -F: '{print 3600*$1+60*$2+$3}' # POSIX awk
date -u -d "@3661" "+%H:%M:%S"
gives 01:01:01
as expected. –
Subsidy I couldn't get Vaulter's/chepner's code to work correctly. I think that the correct code is:
convertsecs() {
h=$(($1/3600))
m=$((($1/60)%60))
s=$(($1%60))
printf "02d:%02d:%02d\n $h $m $s
}
%02d
at the beginning ;-) –
Tildatilde In one line :
show_time () {
if [ $1 -lt 86400 ]; then
date -d@${1} -u '+%Hh:%Mmn:%Ss';
else
echo "$(($1/86400)) days $(date -d@$(($1%86400)) -u '+%Hh:%Mmn:%Ss')" ;
fi
}
Add days if exist.
Yet another version. Only handles full integers, doesn't pad with 0
:
format_seconds() {
local sec tot r
sec="$1"
r="$((sec%60))s"
tot=$((sec%60))
if [[ "$sec" -gt "$tot" ]]; then
r="$((sec%3600/60))m:$r"
let tot+=$((sec%3600))
fi
if [[ "$sec" -gt "$tot" ]]; then
r="$((sec%86400/3600))h:$r"
let tot+=$((sec%86400))
fi
if [[ "$sec" -gt "$tot" ]]; then
r="$((sec/86400))d:$r"
fi
echo "$r"
}
$ format_seconds 59
59s
$ format_seconds 60
1m:0s
$ format_seconds 61
1m:1s
$ format_seconds 3600
1h:0m:0s
$ format_seconds 236521
2d:17h:42m:1s
This is old post ovbioius -- but, for those who might are looking for the actual time elapsed but in military format (00:05:15:22 - instead of 0:5:15:22 )
!#/bin/bash
num=$1
min=0
hour=0
day=0
if((num>59));then
((sec=num%60))
((num=num/60))
if((num>59));then
((min=num%60))
((num=num/60))
if((num>23));then
((hour=num%24))
((day=num/24))
else
((hour=num))
fi
else
((min=num))
fi
else
((sec=num))
fi
day=`seq -w 00 $day | tail -n 1`
hour=`seq -w 00 $hour | tail -n 1`
min=`seq -w 00 $min | tail -n 1`
sec=`seq -w 00 $sec | tail -n 1`
printf "$day:$hour:$min:$sec"
exit 0
on MacOSX 10.13 Slight edit from @eMPee584 's code to get it all in one GO (put the function in some .bashrc like file and source it, use it as myuptime. For non-Mac OS, replace the T formula by one that gives the seconds since last boot.
myuptime ()
{
local T=$(($(date +%s)-$(sysctl -n kern.boottime | awk '{print $4}' | sed 's/,//g')));
local D=$((T/60/60/24));
local H=$((T/60/60%24));
local M=$((T/60%60));
local S=$((T%60));
printf '%s' "UpTime: ";
[[ $D > 0 ]] && printf '%d days ' $D;
[[ $H > 0 ]] && printf '%d hours ' $H;
[[ $M > 0 ]] && printf '%d minutes ' $M;
[[ $D > 0 || $H > 0 || $M > 0 ]] && printf '%d seconds\n' $S
}
SecToString
function:sec2str() {
if [[ $1 == -v ]] ;then
local -n _result=$2
shift 2
else
local _result
fi
local -a _elapsed
TZ=UTC printf -v _elapsed "%(%Y %j %H %M %S)T" $1
read -a _elapsed <<<"$_elapsed"
_elapsed[0]=$((10#${_elapsed[0]}-1970)) _elapsed[1]=$((10#${_elapsed[1]}-1))
printf -v _elapsed " %dy %dd %.0fh %.0f' %.0f\"" ${_elapsed[@]}
_result=${_elapsed// 0?}
[[ ${_result@A} == _result=* ]] && echo "$_result"
}
In use:
sec2str 864001
10d 1"
sec2str -v str 864001
echo "$str"
10d 1"
for i in 36 1036 91925 23590 7227 38688848;do
sec2str -v str $i
printf "%11d -> %20s\n" $i "$str"
done
36 -> 36"
1036 -> 17' 16"
91925 -> 1d 1h 32' 5"
23590 -> 6h 33' 10"
7227 -> 2h 27"
38688848 -> 1y 82d 18h 54' 8"
µsec
, obtained by ${EPOCHREALTIME/.}
I often use
jobstart=${EPOCHREALTIME/.}
process strong job
jobend=${EPOCHREALTIME/.}
So Elapsed time in µsec
in integer= $(( jobend - jobstart ))
.
musec2str () {
if [[ $1 == -v ]] ;then local -n _result=$2; shift 2
else local _result
fi
local _elapsed=000000$1
printf -v _elapsed "%0.6f" ${_elapsed::-6}.${_elapsed:${#_elapsed}-6}
TZ=UTC printf -v _result "%(%Y %j %H %M %S)T" ${_elapsed%.*}
_elapsed=.${_elapsed#*.}
if shopt -q extglob; then _elapsed=${_elapsed%%*(0)}
else shopt -s extglob; _elapsed=${_elapsed%%*(0)}
shopt -u extglob
fi
_elapsed=${_elapsed%.}
[ "$_elapsed" ] || unset _elapsed
read -ra _result <<<"$_result"
_result[0]=$((10#${_result[0]}-1970)) _result[1]=$((10#${_result[1]}-1))
printf -v _result "%dy %dd %.0fh %.0f' %.0fs" ${_result[@]}
read -ra _result <<<"$_result"
printf -v _result[0] "%s " ${_result[@]#0?}
[ "${_result[0]#*s }" ] && {
[ "$_elapsed" ] && _result[0]=${_result[0]# }0
} || {
[ "$_elapsed" ] && _result[0]=${_result[0]%s }\"
}
_result=("${_result[0]%s }${_elapsed}${_elapsed+\"}")
[[ ${_result@A} == declare\ -a\ _result=* ]] && echo "$_result"
}
Then
musec2str -v str $(( jobend -jobstart ))
echo $str
for i in 14682081 38688848083836 3272661 12189780 23590528902 170295464 938372079
do
musec2str -v str $i
printf "%22u -> %32s\n" $i "$str"
done
14682081 -> 14.682081"
38688848083836 -> 1y 82d 18h 54' 8.083836"
3272661 -> 3.272661"
12189780 -> 12.18978"
23590528902 -> 6h 33' 10.528902"
170295464 -> 2' 50.295464"
938372079 -> 15' 38.372079"
read -r ut _ </proc/uptime
musec2str -v hUt $((${ut/./ * 10**6 + 10**4 * 10#}))
printf 'System is up from %s.\n' "$hUt"
System is up from 21d 21h 47' 20.1".
Note: In last sample, 2nd line could be written:
musec2str -v hUt ${ut/.}0000
For fun:
while read -r ut _ </proc/uptime ; musec2str -v hUt ${ut/.}0000;
! read -p $'\rSystem is up from '"$hUt " -rsn 1 -t .01 _;do :;done;echo
This will print uptime until you hit Any key.
This is my code for zshrc to show executed time of a command. It expect input as milliseconds, but easily can be converted to seconds:
ms_format() {
local s=(d h m s ms) d=(86400000 3600000 60000 1000 1) r= t=$1 i=0 v=
for i in {1..5}; do
v=$(( $t / ${d[i]} ))
[[ $v -gt 0 ]] && t=$(( $t - ($v * ${d[i]}) )) && r+="$v${s[i]} "
[[ -n $r && $2 == "round" ]] && print -P "$r" && return 0
done
print -P "$r"
}
To use, easy as that:
$ ms_format 1024
# will print: 1s 24ms
You can use the option round, and then will show only the bigger value:
$ ms_format 1024 "round"
# will print: 1s
To use second as input, you can do this: (100 seconds input):
$ ms_format $(( 100 * 1000 ))
# will print: 1m 40s
© 2022 - 2024 — McMap. All rights reserved.
date -u -d @${sec} +"%T"
) – Grillroom