Comparing dates of two files in shell - shell

I have two files created at different times.
In my shell scripting program, I want to initialize a variable with the date of the file which was created earlier.
For eg. if file1 was created on 22 April and file2 was created on April 19. my variable should be initialized to 19th April.
How can I do this in bash shell?

Assuming existence of GNU stat (part of GNU coreutils):
if [[ $(stat -c%W <file1>) -lt $(stat -c%W <file2>) ]]
then
EARLIER="$(stat -c%w <file1>)"
else
EARLIER="$(stat -c%w <file1>)"
fi
Note the case of %W (integer) vs. %w (human-readable) is significant.
%W / %w is birth time, since you asked for "creation time". Usually %Y / %y (last modification) or %Z / %z (last change) are more meaningful.
If you need a different format for your date, you could feed the stat output to date, e.g.:
date -d "$(stat -c%w <earlier_file>)" +"%Y-%m-%d")
PS: While you are at the subject of time stamps, please consider RFC 3339 for your formatting, i.e. YYYY-MM-DD HH:MM:DD-TZ, which is non-ambiguous, portable, and sortable.

/bin/date can convert to timestamps, using the %s format, after which date comparison is a straight numeric compare, so the solution to your problem, modulo syntax errors, is:
DATE1=`date -j '22 April' +"%d %B"`
DATE2=`date -j '19 April' +"%d %B"`
if [[ $DATE2 < $DATE1 ]]; then
export VAR=`date -j $DATE1 +"%s"`
else
export VAR=`date -j $DATE2 +"%s"`
fi

Related

Bash: Calculate the time differences in hours from input like "YYYYMMDDHH"

I have two dates in forms like: YYYYMMDDHH and want to calculate the differences (in hours) between these two dates. For example
start_date=1996010100
end_date=1996010122
which stands for two dates: 1996-01-01 00:00:00 and 1996-01-01 22:00:00. I want to use date to calculate the difference in hours, the result shall be 22 hours. I tried with
START=$(date -d "$start_date" +"%s")
END=$(date -d "$end_date" +"%s")
HOURS=$(bc -l <<< "($END - $START) / 3600")
but it failed...
So how can I do this? Thanks!
For performance reasons we want to limit the number of sub-process calls we need to invoke:
use bash substring functionality to convert inputs into usable date/time strings
use bash math to replace bc call
bash substring functionality to break the inputs into a usable date/time format, eg:
# convert to usable date/time format:
$ start_date=1996010100
$ echo "${start_date:0:4}-${start_date:4:2}-${start_date:6:2} ${start_date:8:2}:00:00"
1996-01-01 00:00:00
# convert to epoch/seconds:
$ start=$(date -d "${start_date:0:4}-${start_date:4:2}-${start_date:6:2} ${start_date:8:2}:00:00" +"%s")
$ echo $start
820476000
Applying to ${end_date} and using bash math:
$ end_date=1996010122
$ end=$(date -d "${end_date:0:4}-${end_date:4:2}-${end_date:6:2} ${end_date:8:2}:00:00" +"%s")
$ echo $end
820555200
$ hours=$(( (end - start) / 3600))
$ echo $hours
22
This leaves us with 2 sub-process calls ($(date ...)). While other languages/tools (awk, perl, etc) can likely speed this up a bit, if you need to store the result in a bash variable then you're looking at needing at least 1 sub-process call (ie, hours=$(awk/perl/??? ...)).
If performance is really important (eg, needing to perform this 1000's of times) take a look at this SO answer that uses a fifo, background date process and io redirection ... yeah, a bit more coding and a bit more convoluted but also a bit faster for large volumes of operations.
busybox date can do the trick
start_date=1996010100
end_date=1996010122
START=$(busybox date -D "%Y%m%d%H" -d "$start_date" +"%s")
END=$(busybox date -D "%Y%m%d%H" -d "$end_date" +"%s")
HOURS=$(bc -l <<< "scale=0;($END - $START) / 3600")
echo $HOURS
If it's possible for you to use a more fully-featured scripting language like Python, it'll provide a much more pleasant and understandable date parsing experience, and is probably installed by default (datetime is also a standard Python library)
Structured with shell vars
start_date=1996010100
end_date=1996010122
python -c "import datetime ; td = datetime.datetime.strptime('${end_date}', '%Y%m%d%H') - datetime.datetime.strptime('${start_date}', '%Y%m%d%H') ; print(int(td.total_seconds() / 3600))"
Structured to read dates and format code from stdin
echo '%Y%m%d%H' 1996010100 1996010122 | python -c "import datetime,sys ; fmt, date_start, date_end = sys.stdin.read().strip().split() ; td = datetime.datetime.strptime(date_end, fmt) - datetime.datetime.strptime(date_start, fmt) ; print(int(td.total_seconds() / 3600))"
Should work with both Python 3 and Python 2.7
format codes available here (1989 C standard)
https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes
which stands for two dates: 1996-01-01 00:00:00
So convert it to that form if it stands for it.
start_date=1996010100
start_date=$(sed -E 's/(....)(..)(..)(..)/\1-\2-\3 \4:00:00/' <<<"$start_date")
start=$(date -d "$start_date" +"%s")
and the same with end.
the most simple way is to install "dateutils" using this command
sudo apt-get install dateutils
Run these commands to get the difference in seconds:
dateutils.ddiff -i '%Y%m%d%H%M%S' 20200817040001 20210817040101
output:
31536060s
next step: Simply divide by 86400 to get the number of days or similarly for hours and minutes :)

Want to use a bash for loop to grab descending months

We have a system that would have a cron job that deletes files up to two months ago. I'm trying to write a script to automate this, but I'm fairly new to bash scripting and was wondering if anyone would be able to help. Our files are in %m%Y format and I would be moving them to another directory and then deleting that directory. So for instance since we are in August (082020), I want to move all files up to June (062020) starting this year in Jan (012020).
Here is my script so far, I am basically just trying to print 012020-062020, can anyone let me know if I am on the right track?
#!/bin/bash
MONTHYEAR=$(date +%m%Y)
DELUPTO=$(expr $(date +%m%Y) - 20000)
CURRENTYEAR=$(date +%Y)
for (( i=$DELUPTO; i>=01 + $CURRENTYEAR; $(expr $i - 10000) ))
do
echo "$i"
done
You should loop from the format yyyydd, so start with
for (( i=202006; i>=202001; i-- )); do
echo "${i:4:2}${i:0:4}"
done
It is up to you how you want to achieve this:
yearmonth=$(date +%Y%m)
or
MONTHYEAR=$(date +%m%Y)
yearmonth=${MONTHYEAR:2:4}${MONTHYEAR:0:2}
You know the month and year, extract those values and then turn it into a stamp, but you will need to insert a day value so I would make it say the 1st:
Example of converting timestamps:
# date -d "8/1/2020" +"%s"
1596254400
# date -d #1596254400 +"%b %d %Y %H:%M:%S"
Aug 01 2020 00:00:00
Then create a time stamp of now minutes X days:
date +%s -d "60 days ago"
Once you have common values to compare, Then compare them and if less than 60 days delete Pseudo code:
del_date=$(date +%s -d "60 days ago")
for each file in directory:
#get month and day from file name here, then
file_date=$(date -d "${fmonth}/1/${fyear}" +"%s")
if [[ $file_date -lt $del_date ]] ;then
echo "Older than 60 days by name"
fi
done
Note: It would probably be better to delete files by checking their ages in the system using stat command opposed to reading the details of file name.

Script shell check validate date format YYYYMMDDHHMMSS

i want to check if Date is valid or not using Script shell.
I tried this:
date "+%d/%m/%Y" -d "09/10/2013" > /dev/null 2>&1
is_valid=$?
It works fine for me when it comes to dd/MM/YYYY format. I Tried
date "+%Y%m%d%H%M%S" -d "20191001041253" > /dev/null 2>&1
is_valid=$?
for YYYYmmddHHMMSS but it doesn't work.
Could someone please guide me to check if a date is valid or not with the YYYYmmddHHMMSS.
GNU date has a very relaxed syntax as to how to read input. The "+..." is only for specifying the output of it, you cannot in any way specify how should it read/parse the input.
You need to first convert your input into something GNU date can understand. I found that the format mentioned in the DATE STRING section in man page seems to be most reliable.
s=20191001041253
# 2019-10-01 04:12:53
s="${s::4}-${s:4:2}-${s:6:2} ${s:8:2}:${s:10:2}:${s:12:2}"
if date --date="$s" >/dev/null 2>/dev/null; then
echo "Its ok"
else
echo "its invalid"
fi
Note that bsd date has an -f options that allows specifying input format. I wish such option would be available with gnu date.
The 'date' utility accept date values in many formats (look at info date, 'Date Input formats' section). The bad news is that the format you need ( 'YYYYmmddHHMMSS') is not supported. The good news is that similar format (ISO compact format 'YYYYmmdd< HH:MM:SS') is supported.
date -d '20191001 05:06:07'
Tue Oct 1 05:06:07 IDT 2019
If you want to LIMIT the input date format to accept ONLY YYYYmmddHHMMSS, you actually have to write a small filter
input=20191001050607
# Convert to YYYYmmdd HH:MM:SS
t="${input:0:8} ${input:8:2}:${input:10:2}:${input:12:2}"
is_valid=
# Validate; Check for 14 digits + try to convert to date
[[ "$input" =~ ^[0-9]{14}$ ]] && date -d "$t" > /dev/null 2>&1 && is_valid=YES
echo "$is_valid"
Try changing it to ISO format:-
$ date "+%Y%m%d%H%M%S" -d "2019-10-01 04:12:53"
20191001041253
$ echo $?
0

Bash -1 year from date

Year=`date '+%Y'`
RTRN1=$?
This returns the current date in the logs, however i want to return the year before, so instead of this returning 2017 i want 2016.
Any help appreciated! Thanks
For GNU date utility: use -d (--date) option to adjust the date:
Year=$(date +%Y -d'1 year ago')
echo $Year
2016
As we have bash, it's possible to use let
let YEAR=`date +%Y`-1
echo $YEAR
You can always capture the year with date +'%Y'. You can subtract 1 with the POSIX arithmetic operator, e.g.
$ echo $(($(date +%Y) - 1))
2016
You can also use the POSIX compliant expr math operators, e.g.
$ expr $(date +%Y) - 1
2016
(note: with expr you must leave a space between the math operator and the values)
The GNU date operator -d with '1 year ago' will work as specified in the comments and other answer, along with let dt=$(date +%Y)-1; echo $dt as specified in the other answer (no spaces allowed with let).
Of all the choices, if I didn't have GNU date, I'd pick the POSIX arithmetic operator $((...)) with a date command substitution minus 1.

Get remaining seconds from input HH:mm in bash (OSX)

I want write a simple bash script. Input is "HH:mm" string and the output is remaining seconds to that time, for example
$ getsec.sh 12:55 # now it's 12:30. so 25 minutes left.
1500
I thought using date command would probably the solution of this. But it seems like it doesn't exist the simple way that I can use.
(Added after checking some answers)
It looks like the date command depends on OS and version.
I use OSX, and the result of the suggested answer is as follows.
$ date -d '12:55' '+%s'
usage: date [-jnu] [-d dst] [-r seconds] [-t west] [-v[+|-]val[ymwdHMS]] ...
[-f fmt date | [[[mm]dd]HH]MM[[cc]yy][.ss]] [+format]
The *BSD (and thus OSX) date command has a different option syntax than GNU date, which is ubiquitous on Linux and available on many other platforms (including OSX, with Homebrew, Fink, etc).
now=$(date -j +%s)
then=$(date -j -f '%H:%M' 12:55 +%s)
echo "$((then-now)) seconds left"
For a portable solution, maybe use a portable scripting language instead.
You can do:
ts=$(date -d '12:55' '+%s')
now=$(date '+%s')
diff=$((ts-now))
This script accepts the target time as a command line argument (accessed in the script as the parameter $1
#!/bin/bash
current_time=$(date +%s)
target_time=$(date +%s --date="$1")
# evaluate the difference using an arithmetic expression
echo "seconds remaining to target time: "$(( target_time - current_time ))
usage:
% ./get_sec.sh "22:00"
output:
seconds remaining to target time: 16151

Resources