Reputation: 171321
I print the start and end time using date +"%T"
, which results in something like:
10:33:56
10:36:10
How could I calculate and print the difference between these two?
I would like to get something like:
2m 14s
Upvotes: 327
Views: 426870
Reputation: 104
I can't believe you guys have no clear answer for milliseconds:
get_msec() {
echo $(($(date +%s%N)/1000000))
}
START=$(get_msec)
echo "OMG"
echo "DURATION=" $(( $(get_msec)-$START )) "ms"
Upvotes: 0
Reputation: 19266
Bash has a handy SECONDS
builtin variable that tracks the number of seconds that have passed since the shell was started. This variable retains its properties when assigned to, and the value returned after the assignment is the number of seconds since the assignment plus the assigned value.
Thus, you can just set SECONDS
to 0 before starting the timed event, simply read SECONDS
after the event, and do the time arithmetic before displaying.
#!/usr/bin/env bash
SECONDS=0
# do some work
duration=$SECONDS
echo "$((duration / 60)) minutes and $((duration % 60)) seconds elapsed."
As this solution doesn't depend on date +%s
(which is a GNU extension), it's portable to all systems supported by Bash.
Documentation:
SECONDS This variable expands to the number of seconds since the shell was started. Assignment to this variable resets the count to the value assigned, and the expanded value becomes the value assigned plus the number of seconds since the assignment. The number of seconds at shell invocation and the current time are always determined by querying the system clock. If SECONDS is unset, it loses its special properties, even if it is subsequently reset. [https://www.gnu.org/software/bash/manual/bash.html]
Upvotes: 701
Reputation: 2807
If you already have the time differential calculated, and they're shorter than 1 day, here's a really fringe use case of bc
to kinda-sorta format the output into a
HH MM SS.xxxx
24-hour
format, keeping in mind that the digits to right of decimal point are printed in base 60
jot -w 'obase = 60; %.3f' - 1.3219567 300 73.6543211 | bc
01.19 19
01 14.58 33
02 28.37 51
03 42.17 06 # 3 mins 42 secs
04 55.56 20
...
...
19 38 10.54 32 # 19 hrs 38 mins 10 secs
#
# (or 7:38pm, if it's representing absolute time)
But it's definitely a quick-n-dirty way to get a ballpark figure.
Upvotes: 0
Reputation: 7627
Define this function (say, in ~/.bashrc):
time::clock() {
[ -z "$ts" ]&&{ ts=`date +%s%N`;return;}||te=`date +%s%N`
printf "%6.4f" $(echo $((te-ts))/1000000000 | bc -l)
unset ts te
}
Now you can measure execution time of parts of your scripts:
$ cat script.sh
# ... code ...
time::clock
sleep 0.5
echo "Total time: ${time::clock}"
# ... more code ...
$ ./script.sh
Total time: 0.5060
It is very useful to find execution bottlenecks.
Upvotes: 3
Reputation: 1276
Generalizing nisetama's solution using GNU date (Ubuntu 14.04 LTS (Trusty Tahr)):
start=`date`
# <processing code>
stop=`date`
duration=`date -ud@$(($(date -ud"$stop" +%s)-$(date -ud"$start" +%s))) +%T`
echo $start
echo $stop
echo $duration
yielding:
Wed Feb 7 12:31:16 CST 2018
Wed Feb 7 12:32:25 CST 2018
00:01:09
Upvotes: 2
Reputation: 1158
Here is a solution using only the date
commands capabilities using "ago", and not using a second variable to store the finish time:
#!/bin/bash
# Save the current time
start_time=$( date +%s.%N )
# Tested program
sleep 1
# The current time after the program has finished
# minus the time when we started, in seconds.nanoseconds
elapsed_time=$( date +%s.%N --date="$start_time seconds ago" )
echo elapsed_time: $elapsed_time
This gives:
$ ./time_elapsed.sh
elapsed_time: 1.002257120
Upvotes: 8
Reputation: 8863
Another option is to use datediff
from dateutils
(http://www.fresse.org/dateutils/#datediff):
$ datediff 10:33:56 10:36:10
134s
$ datediff 10:33:56 10:36:10 -f%H:%M:%S
0:2:14
$ datediff 10:33:56 10:36:10 -f%0H:%0M:%0S
00:02:14
You could also use Gawk. mawk
1.3.4 also has strftime
and mktime
, but older versions of mawk
and nawk
don't.
$ TZ=UTC0 awk 'BEGIN{print strftime("%T",mktime("1970 1 1 10 36 10")-mktime("1970 1 1 10 33 56"))}'
00:02:14
Or here's another way to do it with GNU date
:
$ date -ud@$(($(date -ud'1970-01-01 10:36:10' +%s)-$(date -ud'1970-01-01 10:33:56' +%s))) +%T
00:02:14
Upvotes: 21
Reputation: 46796
Here's my Bash implementation (with bits taken from other Stack Overflow answers ;-)
function countTimeDiff() {
timeA=$1 # 09:59:35
timeB=$2 # 17:32:55
# feeding variables by using read and splitting with IFS
IFS=: read ah am as <<< "$timeA"
IFS=: read bh bm bs <<< "$timeB"
# Convert hours to minutes.
# The 10# is there to avoid errors with leading zeros
# by telling bash that we use base 10
secondsA=$((10#$ah*60*60 + 10#$am*60 + 10#$as))
secondsB=$((10#$bh*60*60 + 10#$bm*60 + 10#$bs))
DIFF_SEC=$((secondsB - secondsA))
echo "The difference is $DIFF_SEC seconds.";
SEC=$(($DIFF_SEC%60))
MIN=$((($DIFF_SEC-$SEC)%3600/60))
HRS=$((($DIFF_SEC-$MIN*60)/3600))
TIME_DIFF="$HRS:$MIN:$SEC";
echo $TIME_DIFF;
}
$ countTimeDiff 2:15:55 2:55:16
The difference is 2361 seconds.
0:39:21
It was not tested and may be buggy.
Upvotes: -2
Reputation:
To measure elapsed time (in seconds) we need:
Bash variable SECONDS (if SECONDS is unset it loses its special property).
Setting the value of SECONDS to 0:
SECONDS=0
sleep 1 # Process to execute
elapsedseconds=$SECONDS
Storing the value of the variable SECONDS
at the start:
a=$SECONDS
sleep 1 # Process to execute
elapsedseconds=$(( SECONDS - a ))
Bash printf option %(datefmt)T
:
a="$(TZ=UTC0 printf '%(%s)T\n' '-1')" ### `-1` is the current time
sleep 1 ### Process to execute
elapsedseconds=$(( $(TZ=UTC0 printf '%(%s)T\n' '-1') - a ))
The bash internal printf
can do that directly:
$ TZ=UTC0 printf '%(%H:%M:%S)T\n' 12345
03:25:45
similarly
$ elapsedseconds=$((12*60+34))
$ TZ=UTC0 printf '%(%H:%M:%S)T\n' "$elapsedseconds"
00:12:34
but this will fail for durations of more than 24 hours, as we actually print a wallclock time, not really a duration:
$ hours=30;mins=12;secs=24
$ elapsedseconds=$(( ((($hours*60)+$mins)*60)+$secs ))
$ TZ=UTC0 printf '%(%H:%M:%S)T\n' "$elapsedseconds"
06:12:24
For the lovers of detail, from bash-hackers.org:
%(FORMAT)T
outputs the date-time string resulting from using FORMAT as a format string forstrftime(3)
. The associated argument is the number of seconds since Epoch, or -1 (current time) or -2 (shell startup time). If no corresponding argument is supplied, the current time is used as default.
So you may want to just call textifyDuration $elpasedseconds
where textifyDuration
is yet another implementation of duration printing:
textifyDuration() {
local duration=$1
local shiff=$duration
local secs=$((shiff % 60)); shiff=$((shiff / 60));
local mins=$((shiff % 60)); shiff=$((shiff / 60));
local hours=$shiff
local splur; if [ $secs -eq 1 ]; then splur=''; else splur='s'; fi
local mplur; if [ $mins -eq 1 ]; then mplur=''; else mplur='s'; fi
local hplur; if [ $hours -eq 1 ]; then hplur=''; else hplur='s'; fi
if [[ $hours -gt 0 ]]; then
txt="$hours hour$hplur, $mins minute$mplur, $secs second$splur"
elif [[ $mins -gt 0 ]]; then
txt="$mins minute$mplur, $secs second$splur"
else
txt="$secs second$splur"
fi
echo "$txt (from $duration seconds)"
}
To get formatted time we should use an external tool (GNU date) in several ways to get up to almost a year length and including nanoseconds.
There isn't any need for external arithmetic, do it all in one step inside date
:
date -u -d "0 $FinalDate seconds - $StartDate seconds" +"%H:%M:%S"
Yes, there is a 0
zero in the command string. It is needed.
That's assuming you could change the date +"%T"
command to a date +"%s"
command so the values will be stored (printed) in seconds.
Note that the command is limited to:
$StartDate
and $FinalDate
seconds.$FinalDate
is bigger (later in time) than $StartDate
.If you must use the 10:33:56
string, well, just convert it to seconds. Also, the word seconds could be abbreviated as sec:
string1="10:33:56"
string2="10:36:10"
StartDate=$(date -u -d "$string1" +"%s")
FinalDate=$(date -u -d "$string2" +"%s")
date -u -d "0 $FinalDate sec - $StartDate sec" +"%H:%M:%S"
Note that the seconds time conversion (as presented above) is relative to the start of "this" day (Today).
The concept could be extended to nanoseconds, like this:
string1="10:33:56.5400022"
string2="10:36:10.8800056"
StartDate=$(date -u -d "$string1" +"%s.%N")
FinalDate=$(date -u -d "$string2" +"%s.%N")
date -u -d "0 $FinalDate sec - $StartDate sec" +"%H:%M:%S.%N"
If is required to calculate longer (up to 364 days) time differences, we must use the start of (some) year as reference and the format value %j
(the day number in the year):
Similar to:
string1="+10 days 10:33:56.5400022"
string2="+35 days 10:36:10.8800056"
StartDate=$(date -u -d "2000/1/1 $string1" +"%s.%N")
FinalDate=$(date -u -d "2000/1/1 $string2" +"%s.%N")
date -u -d "2000/1/1 $FinalDate sec - $StartDate sec" +"%j days %H:%M:%S.%N"
Output:
026 days 00:02:14.340003400
Sadly, in this case, we need to manually subtract 1
ONE from the number of days.
The date command view the first day of the year as 1.
It is not that difficult...
a=( $(date -u -d "2000/1/1 $FinalDate sec - $StartDate sec" +"%j days %H:%M:%S.%N") )
a[0]=$((10#${a[0]}-1)); echo "${a[@]}"
The use of long number of seconds is valid and documented on 21.1.7 Examples of date.
A tool used in smaller devices (a very small executable to install): BusyBox.
Either make a link to BusyBox called date:
$ ln -s /bin/busybox date
Use it then by calling this date
(place it in a PATH included directory).
Or make an alias like:
$ alias date='busybox date'
BusyBox date has a nice option: -D to receive the format of the input time.
That opens up a lot of formats to be used as time. Using the -D option we can convert the time 10:33:56 directly:
date -D "%H:%M:%S" -d "10:33:56" +"%Y.%m.%d-%H:%M:%S"
And as you can see from the output of the Command above, the day is assumed to be "today". To get the time starting on epoch:
$ string1="10:33:56"
$ date -u -D "%Y.%m.%d-%H:%M:%S" -d "1970.01.01-$string1" +"%Y.%m.%d-%H:%M:%S"
1970.01.01-10:33:56
BusyBox date can even receive the time (in the format above) without -D:
$ date -u -d "1970.01.01-$string1" +"%Y.%m.%d-%H:%M:%S"
1970.01.01-10:33:56
And the output format could even be seconds since epoch.
$ date -u -d "1970.01.01-$string1" +"%s"
52436
For both times, and a little bash math (BusyBox can not do the math, yet):
string1="10:33:56"
string2="10:36:10"
t1=$(date -u -d "1970.01.01-$string1" +"%s")
t2=$(date -u -d "1970.01.01-$string2" +"%s")
echo $(( t2 - t1 ))
Or formatted:
$ date -u -D "%s" -d "$(( t2 - t1 ))" +"%H:%M:%S"
00:02:14
Upvotes: 162
Reputation: 16832
I needed a time difference script for use with mencoder
(its --endpos
is relative), and my solution is to call a Python script:
$ ./timediff.py 1:10:15 2:12:44
1:02:29
Fractions of seconds are also supported:
$ echo "diff is `./timediff.py 10:51.6 12:44` (in hh:mm:ss format)"
diff is 0:01:52.4 (in hh:mm:ss format)
And it can tell you that the difference between 200 and 120 is 1h 20m:
$ ./timediff.py 120:0 200:0
1:20:0
And it can convert any (probably fractional) number of seconds or minutes or hours to hh:mm:ss:
$ ./timediff.py 0 3600
1:00:0
$ ./timediff.py 0 3.25:0:0
3:15:0
timediff.py:
#!/usr/bin/python
import sys
def x60(h, m):
return 60*float(h) + float(m)
def seconds(time):
try:
h, m, s = time.split(':')
return x60(x60(h, m), s)
except ValueError:
try:
m, s = time.split(':')
return x60(m, s)
except ValueError:
return float(time)
def difftime(start, end):
d = seconds(end) - seconds(start)
print '%d:%02d:%s' % (d/3600,d/60%60,('%02f' % (d%60)).rstrip('0').rstrip('.'))
if __name__ == "__main__":
difftime(sys.argv[1], sys.argv[2])
Upvotes: 1
Reputation: 11
With GNU Units:
units
Output:
2411 units, 71 prefixes, 33 nonlinear units
You have: (10hr+36min+10s)-(10hr+33min+56s)
You want: s
* 134
/ 0.0074626866
You have: (10hr+36min+10s)-(10hr+33min+56s)
You want: min
* 2.2333333
/ 0.44776119
Upvotes: 1
Reputation: 153
I came across it today while working on a script that would take dates and times from a log file and compute the delta. The script below is certainly overkill, and I highly recommend checking my logic and maths.
#!/bin/bash
dTime=""
tmp=""
#firstEntry="$(head -n 1 "$LOG" | sed 's/.*] \([0-9: -]\+\).*/\1/')"
firstEntry="2013-01-16 01:56:37"
#lastEntry="$(tac "$LOG" | head -n 1 | sed 's/.*] \([0-9: -]\+\).*/\1/')"
lastEntry="2014-09-17 18:24:02"
# I like to make the variables easier to parse
firstEntry="${firstEntry//-/ }"
lastEntry="${lastEntry//-/ }"
firstEntry="${firstEntry//:/ }"
lastEntry="${lastEntry//:/ }"
# Remove the following lines in production
echo "$lastEntry"
echo "$firstEntry"
# Compute days in last entry
for i in `seq 1 $(echo $lastEntry|awk '{print $2}')`; do {
case "$i" in
1|3|5|7|8|10|12 )
dTime=$(($dTime+31))
;;
4|6|9|11 )
dTime=$(($dTime+30))
;;
2 )
dTime=$(($dTime+28))
;;
esac
} done
# Do leap year calculations for all years between first and last entry
for i in `seq $(echo $firstEntry|awk '{print $1}') $(echo $lastEntry|awk '{print $1}')`; do {
if [ $(($i%4)) -eq 0 ] && [ $(($i%100)) -eq 0 ] && [ $(($i%400)) -eq 0 ]; then {
if [ "$i" = "$(echo $firstEntry|awk '{print $1}')" ] && [ $(echo $firstEntry|awk '{print $2}') -lt 2 ]; then {
dTime=$(($dTime+1))
} elif [ $(echo $firstEntry|awk '{print $2}') -eq 2 ] && [ $(echo $firstEntry|awk '{print $3}') -lt 29 ]; then {
dTime=$(($dTime+1))
} fi
} elif [ $(($i%4)) -eq 0 ] && [ $(($i%100)) -ne 0 ]; then {
if [ "$i" = "$(echo $lastEntry|awk '{print $1}')" ] && [ $(echo $lastEntry|awk '{print $2}') -gt 2 ]; then {
dTime=$(($dTime+1))
} elif [ $(echo $lastEntry|awk '{print $2}') -eq 2 ] && [ $(echo $lastEntry|awk '{print $3}') -ne 29 ]; then {
dTime=$(($dTime+1))
} fi
} fi
} done
# Subtract days in first entry
for i in `seq 1 $(echo $firstEntry|awk '{print $2}')`; do {
case "$i" in
1|3|5|7|8|10|12 )
dTime=$(($dTime-31))
;;
4|6|9|11 )
dTime=$(($dTime-30))
;;
2 )
dTime=$(($dTime-28))
;;
esac
} done
dTime=$(($dTime+$(echo $lastEntry|awk '{print $3}')-$(echo $firstEntry|awk '{print $3}')))
# The above gives number of days for sample. Now
# we need hours, minutes, and seconds
# As a bit of hackery I just put the stuff
# in the best order for use in a for loop
dTime="$(($(echo $lastEntry|awk '{print $6}')-$(echo $firstEntry|awk '{print $6}'))) $(($(echo $lastEntry|awk '{print $5}')-$(echo $firstEntry|awk '{print $5}'))) $(($(echo $lastEntry|awk '{print $4}')-$(echo $firstEntry|awk '{print $4}'))) $dTime"
tmp=1
for i in $dTime; do {
if [ $i -lt 0 ]; then {
case "$tmp" in
1 )
tmp="$(($(echo $dTime|awk '{print $1}')+60)) $(($(echo $dTime|awk '{print $2}')-1))"
dTime="$tmp $(echo $dTime|awk '{print $3" "$4}')"
tmp=1
;;
2 )
tmp="$(($(echo $dTime|awk '{print $2}')+60)) $(($(echo $dTime|awk '{print $3}')-1))"
dTime="$(echo $dTime|awk '{print $1}') $tmp $(echo $dTime|awk '{print $4}')"
tmp=2
;;
3 )
tmp="$(($(echo $dTime|awk '{print $3}')+24)) $(($(echo $dTime|awk '{print $4}')-1))"
dTime="$(echo $dTime|awk '{print $1" "$2}') $tmp"
tmp=3
;;
esac
} fi
tmp=$(($tmp+1))
} done
echo "The sample time is $(echo $dTime|awk '{print $4}') days, $(echo $dTime|awk '{print $3}') hours, $(echo $dTime|awk '{print $2}') minutes, and $(echo $dTime|awk '{print $1}') seconds."
You will get output as follows.
2012 10 16 01 56 37
2014 09 17 18 24 02
The sample time is 700 days, 16 hours, 27 minutes, and 25 seconds.
I modified the script a bit to make it standalone (i.e., just set variable values), but maybe the general idea comes across as well. You'd might want some additional error checking for negative values.
Upvotes: -1
Reputation: 4259
Here's some magic:
time1=14:30
time2=$( date +%H:%M ) # 16:00
diff=$( echo "$time2 - $time1" | sed 's%:%+(1/60)*%g' | bc -l )
echo $diff hours
# outputs 1.5 hours
sed
replaces a :
with a formula to convert to 1/60. Then the time calculation that is made by bc
.
Upvotes: 7
Reputation: 577
Following on from Daniel Kamil Kozar's answer, to show hours/minutes/seconds:
echo "Duration: $(($DIFF / 3600 )) hours $((($DIFF % 3600) / 60)) minutes $(($DIFF % 60)) seconds"
So the full script would be:
date1=$(date +"%s")
date2=$(date +"%s")
DIFF=$(($date2-$date1))
echo "Duration: $(($DIFF / 3600 )) hours $((($DIFF % 3600) / 60)) minutes $(($DIFF % 60)) seconds"
Upvotes: 11
Reputation: 819
I'd like to propose another way that avoids recalling the date
command. It may be helpful in case if you have already gathered timestamps in the %T
date format:
ts_get_sec()
{
read -r h m s <<< $(echo $1 | tr ':' ' ' )
echo $(((h*60*60)+(m*60)+s))
}
start_ts=10:33:56
stop_ts=10:36:10
START=$(ts_get_sec $start_ts)
STOP=$(ts_get_sec $stop_ts)
DIFF=$((STOP-START))
echo "$((DIFF/60))m $((DIFF%60))s"
We can even handle milliseconds in the same way.
ts_get_msec()
{
read -r h m s ms <<< $(echo $1 | tr '.:' ' ' )
echo $(((h*60*60*1000)+(m*60*1000)+(s*1000)+ms))
}
start_ts=10:33:56.104
stop_ts=10:36:10.102
START=$(ts_get_msec $start_ts)
STOP=$(ts_get_msec $stop_ts)
DIFF=$((STOP-START))
min=$((DIFF/(60*1000)))
sec=$(((DIFF%(60*1000))/1000))
ms=$(((DIFF%(60*1000))%1000))
echo "${min}:${sec}.$ms"
Upvotes: 12
Reputation: 23929
Here is how I did it:
START=$(date +%s);
sleep 1; # Your stuff
END=$(date +%s);
echo $((END-START)) | awk '{print int($1/60)":"int($1%60)}'
It is really simple. Take the number of seconds at the start, take the number of seconds at the end, and print the difference in minutes:seconds.
Upvotes: 50
Reputation: 534
As of date (GNU Core Utilities) 7.4, you can now use -d
to do arithmetic:
$ date -d -30days
Sat Jun 28 13:36:35 UTC 2014
$ date -d tomorrow
Tue Jul 29 13:40:55 UTC 2014
The units you can use are days, years, months, hours, minutes, and seconds :
$ date -d tomorrow+2days-10minutes
Thu Jul 31 13:33:02 UTC 2014
Upvotes: 8
Reputation: 167
#!/bin/bash
START_TIME=$(date +%s)
sleep 4
echo "Total time elapsed: $(date -ud "@$(($(date +%s) - $START_TIME))" +%T) (HH:MM:SS)"
$ ./total_time_elapsed.sh
Total time elapsed: 00:00:04 (HH:MM:SS)
Upvotes: 3
Reputation: 2516
date
can give you the difference and format it for you (OS X options shown)
date -ujf%s $(($(date -jf%T "10:36:10" +%s) - $(date -jf%T "10:33:56" +%s))) +%T
# 00:02:14
date -ujf%s $(($(date -jf%T "10:36:10" +%s) - $(date -jf%T "10:33:56" +%s))) \
+'%-Hh %-Mm %-Ss'
# 0h 2m 14s
Some string processing can remove those empty values
date -ujf%s $(($(date -jf%T "10:36:10" +%s) - $(date -jf%T "10:33:56" +%s))) \
+'%-Hh %-Mm %-Ss' | sed "s/[[:<:]]0[hms] *//g"
# 2m 14s
This won't work if you place the earlier time first. If you need to handle that, change $(($(date ...) - $(date ...)))
to $(echo $(date ...) - $(date ...) | bc | tr -d -)
Upvotes: 3
Reputation: 31
Or wrap it up a bit
alias timerstart='starttime=$(date +"%s")'
alias timerstop='echo seconds=$(($(date +"%s")-$starttime))'
Then this works.
timerstart; sleep 2; timerstop
seconds=2
Upvotes: 3
Reputation: 39
% start=$(date +%s)
% echo "Diff: $(date -d @$(($(date +%s)-$start)) +"%M minutes %S seconds")"
Diff: 00 minutes 11 seconds
Upvotes: 3