Time difference in seconds between given two dates - bash

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

Related

Shell script - is there a faster way to write date/time per second between start and end time?

I have this script (which works fine) that will write all the date/time per second, from a start date/time till an end date/time to a file
while read line; do
FIRST_TIMESTAMP="20230109-05:00:01" #this is normally a variable that changes with each $line
LAST_TIMESTAMP="20230112-07:00:00" #this is normally a variable that changes with each $line
date=$FIRST_TIMESTAMP
while [[ $date < $LAST_TIMESTAMP || $date == $LAST_TIMESTAMP ]]; do
date2=$(echo $date |sed 's/ /-/g' |sed "s/^/'/g" |sed "s/$/', /g")
echo "$date2" >> "OUTPUTFOLDER/output_LABELS_$line"
date=$(date -d "$date +1 sec" +"%Y%m%d %H:%M:%S")
done
done < external_file
However this sometimes needs to run 10 times, and the start date/time and end date/time sometimes lies days apart.
Which makes the script take a long time to write all that data.
Now I am wondering if there is a faster way to do this.
Avoid using a separate date call for each date. In the next example I added a safety parameter maxloop, avoiding loosing resources when the dates are wrong.
#!/bin/bash
awkdates() {
maxloop=1000000
awk \
-v startdate="${first_timestamp:0:4} ${first_timestamp:4:2} ${first_timestamp:6:2} ${first_timestamp:9:2} ${first_timestamp:12:2} ${first_timestamp:15:2}" \
-v enddate="${last_timestamp:0:4} ${last_timestamp:4:2} ${last_timestamp:6:2} ${last_timestamp:9:2} ${last_timestamp:12:2} ${last_timestamp:15:2}" \
-v maxloop="${maxloop}" \
'BEGIN {
T1=mktime(startdate);
T2=mktime(enddate);
linenr=1;
while (T1 <= T2) {
printf("%s\n", strftime("%Y%m%d %H:%M:%S",T1));
T1+=1;
if (linenr++ > maxloop) break;
}
}'
}
mkdir -p OUTPUTFOLDER
while IFS= read -r line; do
first_timestamp="20230109-05:00:01" #this is normally a variable that changes with each $line
last_timestamp="20230112-07:00:00" #this is normally a variable that changes with each $line
awkdates >> "OUTPUTFOLDER/output_LABELS_$line"
done < <(printf "%s\n" "line1" "line2")
Using epoch time (+%s and #) with GNU date and GNU seq to
produce datetimes in ISO 8601 date format:
begin=$(date -ud '2023-01-12T00:00:00' +%s)
end=$(date -ud '2023-01-12T00:00:12' +%s)
seq -f "#%.0f" "$begin" 1 "$end" |
date -uf - -Isec
2023-01-12T00:00:00+00:00
2023-01-12T00:00:01+00:00
2023-01-12T00:00:02+00:00
2023-01-12T00:00:03+00:00
2023-01-12T00:00:04+00:00
2023-01-12T00:00:05+00:00
2023-01-12T00:00:06+00:00
2023-01-12T00:00:07+00:00
2023-01-12T00:00:08+00:00
2023-01-12T00:00:09+00:00
2023-01-12T00:00:10+00:00
2023-01-12T00:00:11+00:00
2023-01-12T00:00:12+00:00
if you're using macOS/BSD's date utility instead of the gnu one, the equivalent command to parse would be :
(bsd)date -uj -f '%FT%T' '2023-01-12T23:34:45' +%s
1673566485
...and the reverse process is using -r flag instead of -d, sans "#" prefix :
(bsd)date -uj -r '1673566485' -Iseconds
2023-01-12T23:34:45+00:00
(gnu)date -u -d '#1673566485' -Iseconds
2023-01-12T23:34:45+00:00

Time difference in hours between 2 ISO - 8601 compliant dates

I have 2 dates that are both in ISO - 8061 compliant date format.
echo $START
2019-02-14T16:09:13Z
echo $END
2019-02-14T19:43:12Z
Now I want to find the time difference between both. I would prefer the difference to be in hours.
What Ive tried is :
echo "$(($(date -d "$START" '+%s') - $(date -d "$END" '+%s')))"
But it does not work. Any ideas whats the best way to solve this ?
I'd use the dateutils tools.
Then the answer becomes:
> datediff 2019-02-14T16:09:13Z 2019-02-14T19:43:12Z -f "%H hours"
3 hours
echo "$((($(date -d "$END" '+%s') - $(date -d "$START" '+%s'))/60/60))"
Output:
3
On macOS using the built-in BSD date a more verbose solution is required:
echo "$((($(date -jf '%Y-%m-%dT%H:%M:%SZ' "$END" '+%s') - \
$(date -jf '%Y-%m-%dT%H:%M:%SZ' "$START" +%s))/3600 ))"
Output:
3
Similar approach to #Cyrus's answer of converting to epoch first.

Compute time-difference on millisecond level

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)

Bash script check time differnce between two directory

I am looking for a bash script to who check time difference between two directory? But in $oldtime i can`t get only 20:09:24 i get this "20:09:24.660157390".
How can i get only hours:min only.
#!/bin/bash
oldtime=$(stat -c %y /mnt/dir1| awk '{print $2}' )
ctime=$(date | awk '{print $4}')
DIFF=$($oldtime-$ctime)
if [ $DIFF > 600 ]; then
echo "This directory have more that 10 min"
fi
You could shave off the extra suffix,
but that won't really help you.
20:09:24 - $ctime is not going to work,
because "20:09:24" is not a number.
You have several other syntax errors as well.
To calculate the difference between dates,
you need to work with seconds.
oldseconds=$(stat -c %Y /mnt/dir1)
newseconds=$(date +%s)
if (((newseconds - oldseconds) / 60)); then
echo "This directory have more than 10 min"
fi
Use epoch timestamp and date for converting to that format.
$ date +%H:%M -d #`stat -c %Y /tmp/`
19:19
But it's not recommended to use this format for comparing. Use unix timestamps instead:
if [ $(( $(date +%s) - $(stat -c %Y /tmp) )) -gt 600 ]; then
echo "This directory have more that 10 min"
fi

Mac OSX Shell script parse ISO 8601 date and add one second?

I am trying to figure out how to parse a file with ISO 8601-formatted time stamps, add one second and then output them to a file.
All the examples I have found don't really tell me how to do it with ISO 8601 date/time strings.
Example:
read a csv of times like: "2017-02-15T18:47:59" (some are correct, others are not)
and spit out in a new file "2017-02-15T18:48:00"
mainly just trying to correct a bunch of dates that have 59 seconds at the end to round up to the 1 second mark.
This is my current progress:
#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
# startd=$(date -j -f '%Y%m%d' "$line" +'%Y%m%d');
# echo "$startd";
startd=$(date -j -u -f "%a %b %d %T %Z %Y" $line)
#startd=$(date -j -f '%Y%m%d' "$line" +'%Y%m%d');
echo "$startd";
done < "$1"
Any help would be appreciated
jm666's helpful perl answer will be much faster than your shell loop-based approach.
That said, if you want to make your bash code work on macOS, with its BSD date implementation, here's a solution:
# Define the input date format, which is also used for output.
fmt='%Y-%m-%dT%H:%M:%S'
# Note: -j in all date calls below is needed to suppress setting the
# system date.
while IFS= read -r line || [[ -n "$line" ]]; do
# Parse the line at hand using input format specifier (-f) $fmt,
# and output-format specifier (+) '%s', which outputs a Unix epoch
# timestamp (in seconds).
ts=$(date -j -f "$fmt" "$line" +%s)
# See if the seconds-component (%S) is 59...
if [[ $(date -j -f %s "$ts" +%S) == '59' ]]; then
# ... and, if so, add 1 second (-v +1S).
line=$(date -j -f %s -v +1S "$ts" +"$fmt")
fi
# Output the possibly adjusted timestamp.
echo "$line"
done < "$1"
Note that input dates such as 2017-02-15T18:47:59 are interpreted as local time, because they contain no time-zone information.
This could do the job
perl -MTime::Piece -nlE '$f=q{%Y-%m-%dT%H:%M:%S};$t=Time::Piece->strptime($_,$f)+1;say $t->strftime($f)' < dates.txt
if the dates.txt contains
2017-02-15T18:47:59
2016-02-29T23:59:59
2017-02-28T23:59:59
2015-12-31T23:59:59
2010-10-10T10:10:10
the above produces
2017-02-15T18:48:00
2016-03-01T00:00:00
2017-03-01T00:00:00
2016-01-01T00:00:00
2010-10-10T10:10:11

Resources