Compute time-difference on millisecond level - bash

Assuming the following time-formats:
MM:DD:YYYY hh:mm:ss:nn
How can I compute the difference between two times? I have tried the
following, but it seems to fail.
% Value1='08:27:2018 23:53:50:08'
% Value2='08:28:2018 00:00:08:89'
% echo "$(($(date -d "$Value2" '+%s') - $(date -d "$Value1" '+%s')))"

Update: as the OP changed his format
$ Value1='08:27:2018 23:53:50:08'
$ Value2='08:28:2018 00:00:08:89'
$ Value1=${Value1/://}; Value1=${Value1/://}; Value1=${Value1%:*}.${Value1##*:}
$ Value2=${Value2/://}; Value2=${Value2/://}; Value2=${Value2%:*}.${Value2##*:}
$ echo $(date -d "$Value2" '+%s.%N') - $(date -d "$Value1" '+%s.%N') | bc -l
378.810000000
So all you need to do is convert it to a format that date knows, this is MM/DD/YYYY
Original answer below:
The problem is that your date-time format is not really recognized.
The date format:
$ date -d "08272018"
date: invalid date ‘08272018’
The date command knows many formats, but it is hard for it to distinguish between YYYYMMDD, DDMMYYYY and MMDDYYYY. To be clear, what does 10021002 represent as a date?
In this simple format—without delimiters—date will recognize YYYYMMDD and YYMMDD
$ date -d "20180708"
Sun 8 Jul 00:00:00 UTC 2018
The time format:
The notation HH:MM:SS:ss is by far from standard. What does ss represent, ss 60th of a second? The normal notation would be more HH:MM:SS.sss This will be recognized.
$ date -d "23:53:50:08"
date: invalid date ‘23:53:50:08’
$ date -d "23:53:50.08" "+%a %d %b %T.%N %Z %Y"
Wed 12 Sep 23:53:50.080000000 UTC 2018
So if you get your date format correct, you already get a long way:
% Value1='20180827 23:53:50.08'
% Value2='20180828 00:00:08.89'
% echo "$(($(date -d "$Value2" '+%s') - $(date -d "$Value1" '+%s')))"
378
The sad thing is that we are missing our milliseconds for this you need floating point arithmetic and bash does not support it. But there are ways around that (How do I use floating-point division in bash?)
$ echo $(date -d "$Value2" '+%s.%N') - $(date -d "$Value1" '+%s.%N') | bc -l
378.810000000

I shortened variable names:
v1='08:27:2018 23:53:50:08'
v2='08:28:2018 00:00:08:89'
With GNU date, just stick to one safe input format, you can convert YYYY-MM-DD HH:MM:SS.NN to... anything another. (side note: I love freebsd date, where you can just specify -f option for strptime. I wish I could do that with GNU date). So we can:
v1_epoch=$(date -d "${v1:6:4}-${v1:0:2}-${v1:3:2} ${v1:11:2}:${v1:14:2}:${v1:17:2}.${v1:20}" +%s.%N)
v2_epoch=$(date -d "${v2:6:4}-${v2:0:2}-${v2:3:2} ${v2:11:2}:${v2:14:2}:${v2:17:2}.${v2:20}" +%s.%N)
It will get us values of seconds with nanosecond resolution since epoch time. Now we need to calc a difference, we need to use a tool like bc, cause bash does not support floating point calculations.
diff=$(printf "scale=9; %s - %s\n" "$v2_epoch" "$v1_epoch" | bc)
Now this represents the difference of time we need to represent in hours, minutes, seconds and miliseconds.
printf "%s.%.3s" $(date -d#"$diff" -u +'%H:%M:%S %N')
That's simple, but it will wrap around at 23 hours, so we can do better with bc. The rounding in bc is sometimes unexpected... you need to just get used to unexpected scale=0 lines:
printf "%02d:%02d:%02d.%03d\n" $(printf 'scale=11; a=%s; scale=0; h=a/3600; m=a%%3600/60; s=a%%60/1; ms=a*1000%%1000/1; h \n m \n s \n ms \n' '$diff' | bc -l)
A "neat" oneliner:
$ v1='08:27:2018 23:53:50:08'
$ v2='08:28:2018 00:00:08:89'
$ printf "%02d:%02d:%02d.%03d\n" $(printf 'scale=11; a=%s; scale=0; h=a/3600; m=a%%3600/60; s=a%%60/1; ms=a*1000%%1000/1; h \n m \n s \n ms \n' "$(printf "scale=9; %s - %s\n" "$(date -d "${v2:6:4}-${v2:0:2}-${v2:3:2} ${v2:11:2}:${v2:14:2}:${v2:17:2}.${v2:20}" +%s.%N)" "$(date -d "${v1:6:4}-${v1:0:2}-${v1:3:2} ${v1:11:2}:${v1:14:2}:${v1:17:2}.${v1:20}" +%s.%N)" | bc)" | bc -l)
I guess this could be even shortened with some here strings, but that just harms readability:
printf "%02d:%02d:%02d.%03d\n" $(<<<"scale=11; a=$(<<< "scale=9; $(date -d "${v2:6:4}-${v2:0:2}-${v2:3:2} ${v2:11:2}:${v2:14:2}:${v2:17:2}.${v2:20}" +%s.%N) - $(date -d "${v1:6:4}-${v1:0:2}-${v1:3:2} ${v1:11:2}:${v1:14:2}:${v1:17:2}.${v1:20}" +%s.%N)"$'\n' bc); scale=0; h=a/3600; m=a%3600/60; s=a%60/1; ms=a*1000%1000/1; h"$'\n'"m"$'\n'"s"$'\n'"ms"$'\n' bc -l)
Or you can create a function for conversion:
mydate_read() { date -d "${1:6:4}-${1:0:2}-${1:3:2} ${1:11:2}:${1:14:2}:${1:17:2}.${1:20}" +%s.%N; };
printf "%02d:%02d:%02d.%03d\n" $(<<<"scale=11; a=$(<<< "scale=9; $(mydate_read "$v2") - $(mydate_read "$v1")"$'\n' bc); scale=0; h=a/3600; m=a%3600/60; s=a%60/1; ms=a*1000%1000/1; h"$'\n'"m"$'\n'"s"$'\n'"ms"$'\n' bc -l)
I forgot, we can merge the two bc calls into one:
mydate_read() { date -d "${1:6:4}-${1:0:2}-${1:3:2} ${1:11:2}:${1:14:2}:${1:17:2}.${1:20}" +%s.%N; };
printf "%02d:%02d:%02d.%03d\n" $(printf 'scale=11; a=%s - %s; scale=0; h=a/3600; m=a%%3600/60; s=a%%60/1; ms=a*1000%%1000/1; h \n m \n s \n ms \n' "$(mydate_read "$v2")" "$(mydate_read "$v1")" | bc -l)

Related

Time difference in seconds between given two dates

I have two dates as follows:
2019-01-06 00:02:10 | END
2019-01-05 23:52:00 | START
How could I calculate and print the difference between START and END dates in seconds?
For above case I would like to get something like:
610
Assuming GNU implementation based OS, you can use date's option %s and -d to calculate the time difference in seconds using command substitution and arithmetic operations.
START="2019-01-05 23:52:00"
END="2019-01-06 00:02:10"
Time_diff_in_secs=$(($(date -d "$END" +%s) - $(date -d "$START" +%s)))
echo $Time_diff_in_secs
Output:
610
Hope this helps!!!
With bash and GNU date:
while read d t x x; do
[[ $x == "END" ]] && end="$d $t"
[[ $x == "START" ]] && start="$d $t"
done < file
end=$(date -u -d "$end" '+%s')
start=$(date -u -d "$start" '+%s')
diff=$(($end-$start))
echo "$diff"
Output:
610
See: man date
What you're asking for is difficult verging on impossible using pure bash. Bash doesn't have any date functions of its own. For date processing, most recommendations you'll get will be to use your operating system's date command, but the usage of this command varies by operating system.
In BSD (including macOS):
start="2019-01-05 23:52:00"; end="2019-01-06 00:02:10"
printf '%d\n' $(( $(date -j -f '%F %T' "$end" '+%s') - $(date -j -f '%F %T' "$start" '+%s') ))
In Linux, or anything using GNU date (possibly also Cygwin):
printf '%d\n' $(( $(date -d "$end" '+%s') - $(date -d "$start" '+%s') ))
And just for the fun of it, if you can't (or would prefer not to) use date for some reason, you might be able to get away with gawk:
gawk 'END{ print mktime(gensub(/[^0-9]/," ","g",end)) - mktime(gensub(/[^0-9]/," ","g",start)) }' start="$start" end="$end" /dev/null
The mktime() option parses a date string in almost exactly the format you're providing, making the math easy.
START="2019-01-05 23:52:00"
END="2019-01-06 00:02:10"
parse () {
local data=(`grep -oP '\d+' <<< "$1"`)
local y=$((${data[0]}*12*30*24*60*60))
local m=$((${data[1]}*30*24*60*60))
local d=$((${data[2]}*24*60*60))
local h=$((${data[3]}*60*60))
local mm=$((${data[4]}*60))
echo $((y+m+d+h+mm+${data[5]}))
}
START=$(parse "$START")
END=$(parse "$END")
echo $((END-START)) // OUTPUT: 610
Was trying to solve the same problem on a non-GNU OS, i.e. macOS. I couldn't apply any of the solutions above, although it inspired me to come up with the following solution. I am using some in-line Ruby from within my shell script, which should work out of the box on macOS.
START="2019-01-05 23:52:00"
END="2019-01-06 00:02:10"
SECONDS=$(ruby << RUBY
require 'date'
puts ((DateTime.parse('${END}') - DateTime.parse('${START}')) * 60 * 60 * 24).to_i
RUBY)
echo ${SECONDS}
# 610

How to subtract today's date with a file's modification date in unix?

For example:
echo $(date) - $(date -r sample.txt)
Output:
90 days(for example)
Use %s seconds since 1970-01-01 00:00:00 UTC as in
echo $(expr $(date +%s) - $(date -r sample.txt +%s)) #!/bin/sh
echo $(($(date +%s) - $(date -r sample.txt +%s))) #/bin/bash
One more way
$ ls -l peter.txt
-rwxrw-r--+ 1 pppp qqqq 149 Dec 15 18:39 peter.txt
$ echo "(" $(date +%s) - $(date -r peter.txt +%s) ")/" 86400 | perl -nle ' print eval, " days" '
29.254537037037 days
$

Shell Script for Yesterdays Date

I have tried adding -d "yesterday" but I haven't had any luck getting it to work. Here is what I have for the whole script:
#! /bin/bash
saveDir="TJ"
dd=$(date +"%m-%d-%Y")
for file in *.csv ; do
saveName="${saveDir}/TJ ${dd}.csv"
cut -d',' -f2,14 "$file" > "$saveName"
done
how do I get dd to output yesterdays date instead of the current date?
EDIT: This is what I have now
#! /bin/bash
saveDir="TJ"
dd=$(date --date='yesterday' +'%m-%d-%Y')
for file in *.csv ; do
saveName="${saveDir}/TJ ${dd}.csv"
cut -d',' -f2,14 "$file" > "$saveName"
done
the above is saving the file as TJ .csv but I'm not sure what was done incorrectly
I think you want to use -
$ cat test.sh
#!/bin/bash
dd=$(date --date='yesterday' +'%m-%d-%Y')
echo $dd
$ ./test.sh
12-31-2013
or you could use
$ date -d '1 day ago' +'%m-%d-%Y'
12/31/2013
And for tomorrow -
$ date -d '1 day' +'%m-%d-%Y'
01/02/2014
or
$ date --date='tomorrow'
Thu Jan 2 21:25:00 EST 2014
Get today's date in seconds since epoch. Subtract 86400 to get to yesterday. Then convert yesterday to the string format you want.
today=`date +"%s"`
yesterday=`expr $today - 86400`
dd=`date --date="#${yesterday}" +"%m-%d-%Y"`
Try this
yday=$(date --date yesterday "+%d-%m-%Y")
echo $yday
And If you works in Linux
yday=$(date -d "-1 days" +"%d-%m-%Y")
echo $yday
I tried date -d "yesterday" +%m-%d-%Y on my Linux, it worked fine.
If you are on unix platform, you cannot use -d,
you can get yesterday's date using perl, this is how I do using perl
dd=$(perl -e '($a,$b,$c,$day,$mon,$year,$d,$e,$f) = localtime(time-86400);printf "%02d-%02d-%4d",$day, $mon+1, $year+1900')
echo $dd
01-01-2014
NewDate=`date +"%A %d %B %Y" --date="-1 day"`
echo $NewDate
this will give your yesterday's date (-1)
This will give you tomorrow's date (+1)
even you can check for any values like (+/-) days

Shell script to get difference between two dates

If there are dates as 2010-06-01 and another as 2010-05-15
Using shell script or date command how to get the number of days between the two dates
Thanks..
Using only date and shell arithmetics:
echo $((($(date -d "2010-06-01" "+%s") - $(date -d "2010-05-15" "+%s")) / 86400))
There's a solution that almost works: use the %s date format of GNU date, which prints the number of seconds since 1970-01-01 00:00. These can be subtracted to find the time difference between two dates.
echo $(( ($(date -d 2010-06-01 +%s) - $(date -d 2010-05-15 +%s)) / 86400))
But the following displays 0 in some locations:
echo $((($(date -d 2010-03-29 +%s) - $(date -d 2010-03-28 +%s)) / 86400))
Because of daylight savings time, there are only 23 hours between those times. You need to add at least one hour (and at most 23) to be safe.
echo $((($(date -d 2010-03-29 +%s) - $(date -d 2010-03-28 +%s) + 43200) / 86400))
Or you can tell date to work in a timezone without DST.
echo $((($(date -u -d 2010-03-29 +%s) - $(date -u -d 2010-03-28 +%s)) / 86400))
(POSIX says to call the reference timezone is UTC, but it also says not to count leap seconds, so the number of seconds in a day is always exactly 86400 in a GMT+xx timezone.)
OSX date is different than GNU date. Got it working like this in OSX. This is not portable solution.
start_date=$(date -j -f "%Y-%m-%d" "2010-05-15" "+%s")
end_date=$(date -j -f "%Y-%m-%d" "2010-06-01" "+%s")
echo $(( ($end_date - $start_date) / (60 * 60 * 24) ))
Idea is still same as in the other answers. Convert dates to epoch time, subtract and convert result to days.
Got it
d1=`date +%s -d $1`
d2=`date +%s -d $2`
((diff_sec=d2-d1))
echo - | awk -v SECS=$diff_sec '{printf "Number of days : %d",SECS/(60*60*24)}'
thanks..
Gnu date knows %j to display the day in year:
echo $(($(date -d 2010-06-01 +%j) - $(date -d 2010-05-15 +%j)))
crossing year-boundaries will give wrong results, but since you gave fixed dates ...

Get the date (a day before current time) in Bash

How can I print the date which is a day before current time in Bash?
if you have GNU date and i understood you correctly
$ date +%Y:%m:%d -d "yesterday"
2009:11:09
or
$ date +%Y:%m:%d -d "1 day ago"
2009:11:09
If you have BSD (OSX) date you can do it like this:
date -j -v-1d
Wed Dec 14 15:34:14 CET 2011
Or if you want to do date calculations on an arbitrary date:
date -j -v-1d -f "%Y-%m-%d" "2011-09-01" "+%Y-%m-%d"
2011-08-31
date --date='-1 day'
MAC OSX
For yesterday's date:
date -v-1d +%F
where 1d defines current day minus 1 day. Similarly,
date -v-1w +%F - for previous week date
date -v-1m +%F - for previous month date
IF YOU HAVE GNU DATE,
date --date="1 day ago"
More info: https://www.cyberciti.biz/tips/linux-unix-get-yesterdays-tomorrows-date.html
Sorry not mentioning I on Solaris system.
As such, the -date switch is not available on Solaris bash.
I find out I can get the previous date with little trick on timezone.
DATE=`TZ=MYT+16 date +%Y-%m-%d_%r`
echo $DATE
Well this is a late answer,but this seems to work!!
YESTERDAY=`TZ=GMT+24 date +%d-%m-%Y`;
echo $YESTERDAY;
Advanced Bash-scripting Guide
date +%Y:%m:%d -d "yesterday"
For details about the date format see the man page for date
date --date='-1 day'
date -d "yesterday" '+%Y-%m-%d'
or
date=$(date -d "yesterday" '+%Y-%m-%d')
echo $date
Use Perl instead perhaps?
perl -e 'print scalar localtime( time - 86400 ) . "\n";'
Or, use nawk and (ab)use /usr/bin/adb:
nawk 'BEGIN{printf "0t%d=Y\n", srand()-86400}' | adb
Came across this too ... insane!
/usr/bin/truss /usr/bin/date 2>&1 | nawk -F= '/^time\(\)/ {gsub(/ /,"",$2);printf "0t%d=Y\n", $2-86400}' | adb
date --date='-1 day'
Not very sexy but might do the job:
perl -e 'my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time - 86400);$year += 1900; $mon+= 1; printf ("YESTERDAY: %04d%02d%02d \n", $year, $mon, $mday)'
Formated from "martin clayton" answer.
You could do a simple calculation, pimped with an regex, if the chosen date format is 'YYYYMM':
echo $(($(date +"%Y%m") - 1)) | sed -e 's/99$/12/'
In January of 2020 it will return 201912 ;-)
But, it's only a workaround, when date does not have calculation options and other dateinterpreter options (e.g. using perl) not available ;-)
short answer (GNU format):
date +%Y-%m-%d -d "-2 day"
if you are using OSX, but you need create for GNU compatible, install coreutils first
brew install coreutils
then edit your profile with:
#gnu coreutils first
export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"
re-start your terminal, and now you able to use GNU format!
yesterday=`date -d "-1 day" %F`
Puts yesterday's date in YYYY-MM-DD format into variable $yesterday.
#!/bin/bash
OFFSET=1;
eval `date "+day=%d; month=%m; year=%Y"`
# Subtract offset from day, if it goes below one use 'cal'
# to determine the number of days in the previous month.
day=`expr $day - $OFFSET`
if [ $day -le 0 ] ;then
month=`expr $month - 1`
if [ $month -eq 0 ] ;then
year=`expr $year - 1`
month=12
fi
set `cal $month $year`
xday=${$#}
day=`expr $xday + $day`
fi
echo $year-$month-$day
DST aware solution:
Manipulating the Timezone is possible for changing the clock some hours. Due to the daylight saving time, 24 hours ago can be today or the day before yesterday.
You are sure that yesterday is 20 or 30 hours ago. Which one? Well, the most recent one that is not today.
echo -e "$(TZ=GMT+30 date +%Y-%m-%d)\n$(TZ=GMT+20 date +%Y-%m-%d)" | grep -v $(date +%Y-%m-%d) | tail -1
The -e parameter used in the echo command is needed with bash, but will not work with ksh. In ksh you can use the same command without the -e flag.
When your script will be used in different environments, you can start the script with #!/bin/ksh or #!/bin/bash. You could also replace the \n by a newline:
echo "$(TZ=GMT+30 date +%Y-%m-%d)
$(TZ=GMT+20 date +%Y-%m-%d)" | grep -v $(date +%Y-%m-%d) | tail -1
Try the below code , which takes care of the DST part as well.
if [ $(date +%w) -eq $(date -u +%w) ]; then
tz=$(( 10#$gmthour - 10#$localhour ))
else
tz=$(( 24 - 10#$gmthour + 10#$localhour ))
fi
echo $tz
myTime=`TZ=GMT+$tz date +'%Y%m%d'`
Courtsey Ansgar Wiechers
date +%Y:%m:%d|awk -vFS=":" -vOFS=":" '{$3=$3-1;print}'
2009:11:9

Resources