Calculate time difference between two dates

In Bash, you can also use the magic variable SECONDS:

SECONDS
Each time this parameter is referenced, the number of seconds since shell invocation is returned.

so:

bash -c 'a=$SECONDS; sleep 5; b=$SECONDS; printf "%d seconds passed\n" "$((b-a))"'

outputs 5 seconds passed.

SECONDS only has full-second granularity, though, but so does date +"... %S". With GNU date you could use %N with %s to get time times in nanoseconds.

a=$(date +%s%N)
sleep 1.234
b=$(date +%s%N)
diff=$((b-a))
printf "%s.%s seconds passed\n" "${diff:0: -9}" "${diff: -9:3}"

prints:

1.237 seconds passed

Bash can't do arithmetic on floating point numbers, so you need to either use an external utility to do the calculation, or calculate in units smaller than second like I did above. (The numbers won't fit in 32 bits, so that might not work on a 32-bit system. I'm not sure if Bash uses 64 bits for arithmetic even on 32-bit systems.)

SECONDS also misbehaves if the system time is modified during the measurement period, but really you should only have the time set once during startup and then let NTP adjust the clock to stay in time.


You could use date +%s to get a date format that you can easily do math on (number of seconds [with caveats] since Jan 1 1970 00:00:00Z). You can also convert your ISO-format date back (possibly incorrectly, due to issues like daylight saving time) using date -d "$start" +%s. It's not ideal not only due to DST issues when converting back, but also if someone changes the time while your program is running, or if something like ntp does, then you'll get a wrong answer. Possibly a very wrong one.

A leap second would give you a slightly wrong answer (off by a second).

Unfortunately, checking time's source code, it appears time suffers from the same problems of using wall-clock time (instead of "monotonic" time). Thankfully, Perl is available almost everywhere, and can access the monotonic timers:

start=$(perl -MTime::HiRes=clock_gettime,CLOCK_MONOTONIC -E 'say clock_gettime(CLOCK_MONOTONIC)')

That will get a number of seconds since an arbitrary point in the past (on Linux, since boot, excluding time the machine was suspended). It will count seconds, even if the clock is changed. Since it's somewhat difficult to deal with real numbers (non-integers) in shell, you could do the whole thing in Perl:

#!/usr/bin/perl
use warnings qw(all);
use strict;

use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);

my $cmd   = $ARGV[0];
my $start = clock_gettime(CLOCK_MONOTONIC);

system $cmd @ARGV; # this syntax avoids passing it to "sh -c"

my $exit_code = $?;
my $end       = clock_gettime(CLOCK_MONOTONIC);
printf STDERR "%f\n", $end - $start;

exit($exit_code ? 127 : 0);

I named that "monotonic-time"; and it's used just like "time" (without any arguments of its own). So:

$ monotonic-time sleep 5
5.001831                 # appeared ≈5s later
$ monotonic-time fortune -s
Good news is just life's way of keeping you off balance.
0.017800

Tags:

Shell

Bash