check if argument is a valid date time in shell script - linux

I am writing a bash shell script in Linux, this program will accept a date 08-FEB-18 11.45.18.844 AM as a parameter.
I am wondering if there is a simply way to check if the date time is valid?

You can get a bit creative since you have bash and map the date string into an array which can then be easily parsed with date -d (and the help of another associative array). Once the date/time is mapped to array elements and converted to seconds since epoch with date -d, you simply check the return of the date command to determine if the conversion succeeded or failed. Handle the return appropriately:
#!/bin/bash
[ -n "$1" ] || { ## validate one argument given
printf "error: insufficient input\nusage: %s dd-mmm-yy hh.mm.ss.ms\n" \
"${0##*/}"
exit 1
}
oifs="$IFS" ## save original Internal Field Separator
IFS=$' \t\n-.'; ## set IFS to break on - or .
dt=( $(echo $1) ) ## separate date into indexed array
[ "${#dt[#]}" -lt '7' ] && { ## check all 7 components present
printf "error: date doesn't match dd-mmm-yy hh.mm.ss.ms format\n"
exit 1
}
IFS="$oifs" ## reset original IFS
## create associative array mapping months to numerics
declare -A mo=(
[JAN]=1
[FEB]=2
[MAR]=3
[APR]=4
[MAY]=5
[JUN]=6
[JUL]=7
[AUG]=8
[SEP]=9
[OCT]=10
[NOV]=11
[DEC]=12
)
## any date after 30 considerd 1930, else considered 2000
[ "${dt[2]}" -gt '30' ] && dt[2]=$((${dt[2]} + 1000)) || \
dt[2]=$((${dt[2]} + 2000))
## use date to convert array contents to seconds since epoch
epochsec=$( date -d "${dt[2]}-${mo[${dt[1]}]}-${dt[0]} \
${dt[3]}:${dt[4]}:${dt[5]}.${dt[6]}" +%s )
if [ "$?" -ne '0' ]; then ## check if last return was error
printf "error: invalid date.\n"
else ## output good date
printf "date: %s\n" "$(date -d #$epochsec)"
fi
Example Use/Output
$ bash chkcustomdt.sh "08-FEB-18 11.45.18.844"
date: Thu Feb 8 11:45:18 CST 2018
There are a lot of ways to approach this, this was just the first that came to mind.

Related

Need to generate time values between two given times in bash

So I'm new to bash and I have to make a script that include dynamically echoing lines with changing timestamps HH:MM.
So when I give say
sh run.sh 03:40 05:40
It should echo all the times between the given range
Ex: 03:31 03:32 ........ 05:39 05:40
I know it really simple with loops but I'm not able to figure it out.Any Help?
I have this not so good code which doesnt work as of now.
echo "Enter from Hour:"
read fromhr
echo "Enter from Min:"
read frommin
echo "Enter to Hour:"
read tohr
echo "Enter to Min:"
read tomin
while [ $fromhr -le $tohr ]; do
while [ $frommin -le $tomin ]; do
echo "$fromhr:$frommin"
if [ $frommin -eq 60 ]; then
frommin=0
break
fi
((frommin++))
done
if [ $fromhr -eq 24 ]; then
fromhr=0
fi
((fromhr++))
done
Example 1: Use bash only, faster:
#!/bin/bash
# - input data
fh=03 # from hour
th=05 # to hour
fm=30 # from minute
tm=30 # to minute
for ((h=fh;h<=th;h++)); do
for ((m=0;m<=59;m++)); do
[[ $h -le $fh && $m -lt $fm ]] && continue
[[ $h -ge $th && $m -gt $tm ]] && break
printf '%02d:%02d\n' $h $m
done
done
Example 2: use date to convert back and forth, shorter code, but much slower:
#!/bin/bash
# 1) input data
ft='03:30' # from time
tt='05:30' # to time
# 2) convert to Epochtime (second)
f=`date +%s -d "$ft"` # from
t=`date +%s -d "$tt"` # to
for ((s=f;s<=t;s+=60)); do # 60 seconds = 1 minute
date +%H:%M -d #$s # convert from Epochtime to H:M
done
Note that if you're comparing that from is less than to, you're unlikely to ever reach the hour/date change. Say, iterating from 20:00 to 05:00 won't even happen; and if you iterate from 12:38 to 17:12, there won't be any minutes changed (the inner loop's condition is instantly false). Few steps are suggested.
Change each condition's operator to -ne' rather than-le'.
Move both increments (frommin++ and fromhr++) BEFORE the respective overflow checks (otherwise you will constantly see 24 hours and 60 minutes in the output).
Try this and see if you want to beautify it even more.
Sample code:
#!/bin/bash
# Convert the given start/end time to seconds
# Replace time string with required HH:MM value
start_t=`date -d "03:30" +%s`
end_t=`date -d "03:33" +%s`
while [ ${start_t} -le ${end_t} ]; do
# Print time in HH:MM format
date -d #${start_t} +"%H:%M"
# Increment minute part
start_t=$(expr ${start_t} + 60)
done

Check date whether AM or PM in shell script and take action accordingly

I want to check whether its AM or PM in a shell script. I have tried this:
if ( `date "+%p"` -eq "AM" ); then echo "Yes"; else "NO"; fi
But it shows:
AM: command not found
Also tried = instead of -eq.
Two issues in your current statement:
1) the format specifier %p in GNU date utility may contain lowercase of either AM or PM - depending on your current locale. It's better to use %P specifier as it contains lowercase presentation
2) In bash, -eq - is integer comparison operator, not for string comparison
The right way would be:
if [ `date +%P` = "am" ]; then echo 'Yes'; else echo 'No'; fi
To deal with any locale you may compare the current hour value (given by %H) with midday hour 12:
if [ `date +%H` -lt 12 ]; then echo 'Yes'; else echo 'No'; fi
The locale can specify language- and/or country- specific strings to use in place of am and pm. For example:
$ LC_TIME=hu_HU date +%P
de
$ LC_TIME=fr_FR date +%P
$ LC_TIME=en_EN date +%P
pm
Instead, check the hour to see which half of the day would be used to provide a value for %P:
h=$(date +%H)
if (( h < 12 )); then
echo YES # am
else
echo NO # pm
fi
Or, force a known locale:
ampm=$(LC_TIME=C date +%P)
if [[ $ampm = am ]]; then
...
else
...
fi
here
# String comparision with =
# [ ] instead if ()
if [ `date "+%p"` = "AM" ]; then
echo "Yes";
else
echo "NO"; # Missed echo here
fi

compare time in bash script with ± x min

I'm newbie in bash and need some advice.
I have a .txt file with a time stamp inside that is reloaded every x time, and each time stamps the current date and time.
"20221218-0841"
Now i have build a bash script to check the content and give me an answer if it is the same.
#!/bin/bash
time_status=`cat /root/test.txt | tail -c 14 | cut -d')' -f1`
date_now=`date +%Y%m%d-%H%M`
if [ "$date_now" == "$time_status" ]
then
echo "OK - $time_status "
date +%Y%m%d-%H%M
exit 0
fi
if [ "$date_now" != "$time_status" ]
then
echo "WARNING - $time_status "
date +%Y%m%d-%H%M
exit 1
fi
Everything is ok since now, the script does what it have to do, but i need to get ok for answer and exit with 0 when the time is ± 3 min not exactly the same.
Can someone provide some leads into this?
You can manipulate the date, this way,
# Reading only the '%H%M' part from two variables using read and spitting
# with '-' de-limiter
IFS='-' read _ hourMinuteFromFile <<<"$time_status"
IFS='-' read _ currentHourMinute <<<"$date_now"
# Getting the diff only for the minutes field which form the last two
# parts of the variable above
dateDiff=$(( ${hourMinuteFromFile: -2} - ${currentHourMinute: -2} ))
# Having the condition now for the difference from -3 to 3 as below,
if (( -3 <= ${dateDiff} <=3 )); then
echo "OK - $time_status "
fi
Dry run,
time_status="20170318-1438"
date_now="20170318-1436"
dateDiff=$(( ${hourMinuteFromFile: -2} - ${currentHourMinute: -2} ))
echo "$dateDiff"
2
Another good coding practice is to avoid using ``, back-ticks for command-substitution and use ${..} syntax and also do-away with a the useless use of cat,
time_status=$(tail -c 14 file | cut -d')' -f1)
date_now=$(date +%Y%m%d-%H%M)
You can transform the dates into seconds since 1970-01-01 00:00:00 UTC with date +%s and then perform the usual integer arithmetic on the result.
d1='2017-03-18 10:39:34'
d2='2017-03-18 10:42:25'
s1=$(date +%s -d "$d1")
s2=$(date +%s -d "$d2")
ds=$((s1 - s2))
if [ "$ds" -ge -180 -a "$ds" -le 180 ]
then
echo same
else
echo different
fi

check if argument is a valid date in bash shell

I am writing a bash shell script in Linux, this program will accept a date (mm-dd-yyyy) as a parameter. I am wondering if there is a simply way to check if the date is valid? is there an operator and I can just use test to check?
You can check with date -d "datestring"
So date -d "12/31/2012" is valid, but using hyphens, e.g. date -d "12-31-2012", is not valid for date.
You can also use words: date -d 'yesterday' or date -d '1 week ago' are both valid.
You can extract the day, month, and year values from the input date value MM-DD-YYYY and validate it as the unambiguous (ISO) format YYYY-MM-DD instead (you can validate a DD-MM-YYY formatted date as "correct" using date, e.g. 25-12-2010, but it is not a valid MM-DD-YYY date, hence the need to change the date format first)
A valid date in the correct format is OK
30th November 2005 is valid:
$ DATE=11-30-2005; d=${DATE:3:2}; m=${DATE:0:2}; Y=${DATE:6:4}; echo "year=$Y, month=$m, day=$d"; if date -d "$Y-$m-$d" &> /dev/null; then echo VALID; else echo INVALID; fi
year=2005, month=11, day=30
VALID
$ DATE=11-30-2005; if date -d "${DATE:6:4}-${DATE:0:2}-${DATE:3:2}" &> /dev/null; then echo VALID; else echo INVALID; fi
VALID
An invalid date in the correct format is NOT OK
31st November 2005 does not validate:
$ DATE=11-31-2005; d=${DATE:3:2}; m=${DATE:0:2}; Y=${DATE:6:4}; echo "year=$Y, month=$m, day=$d"; if date -d "$Y-$m-$d" &> /dev/null; then echo VALID; else echo INVALID; fi
year=2005, month=11, day=31
INVALID
$ DATE=11-31-2005; if date -d "${DATE:6:4}-${DATE:0:2}-${DATE:3:2}" &> /dev/null; then echo VALID; else echo INVALID; fi
INVALID
A valid date in the incorrect format is NOT OK
20th April 1979 in DD-MM-YYYY format does not validate as a MM-DD-YYYY date:
$ DATE=20-04-1979; d=${DATE:3:2}; m=${DATE:0:2}; Y=${DATE:6:4}; echo "year=$Y, month=$m, day=$d"; if date -d "$Y-$m-$d" &> /dev/null; then echo VALID; else echo INVALID; fi
year=1979, month=20, day=04
INVALID
$ DATE=20-04-1979; if date -d "${DATE:6:4}-${DATE:0:2}-${DATE:3:2}" &> /dev/null; then echo VALID; else echo INVALID; fi
INVALID
Alternate simpler method: use BASH variable string replace hyphens to slashes
$ DATE="04-30-2005"; [[ $(date -d "${DATE//-/\/}" 2> /dev/null) ]] && echo VALID || echo INVALID
VALID
$ DATE="04-31-2005"; [[ $(date -d "${DATE//-/\/}" 2> /dev/null) ]] && echo VALID || echo INVALID
INVALID
For script use, I kept it as simple as I could. Testing the date value with the date function then checking the exit code of the process.
date -d "02/01/2000" 2>: 1>:; echo $?
This will redirect the standard in and standard error to null : and using echo to return the exit code with $? allows me to check for 0=good date and 1=bad date.
The following worked well for me. Many thanks to my co-worker, Tyler Chamberlain, for the OSX solution.
# Validate a given date/time in Bash on either Linux or Mac (OSX).
# Expected date/time format (in quotes from the command line): YYYY-MM-DD HH:MM:SS
# Example(s): ./this_script "2012-02-29 13:00:00" # IS valid
# ./this_script "2013-02-29 13:00:00" # Is NOT valid
START_DATETIME=$1
function report_error_and_exit
{
local MSG=$1
echo "$MSG" >&2
exit 1
}
# We can use OSTYPE to determine what OS we're running on.
# From http://stackoverflow.com/questions/394230/detect-the-os-from-a-bash-script
# Determine whether the given START_DATETIME is valid.
if [[ "$OSTYPE" == "linux-gnu" ]]
then
# Validate the date on a Linux machine (Redhat or Debian). On Linux, this is
# as easy as adding one minute and checking the return code. If one minute
# cannot be added, then the starting value is not a valid date/time.
date -d "$START_DATETIME UTC + 1 min" +"%F %T" &> /dev/null
test $? -eq 0 || report_error_and_exit "'$START_DATETIME' is not a valid date/time value. $OSTYPE"
elif [[ "$OSTYPE" == "darwin"* ]]
then
# Validate the date on a Mac (OSX). This is done by adding and subtracting
# one minute from the given date/time. If the resulting date/time string is identical
# to the given date/time string, then the given date/time is valid. If not, then the
# given date/time is invalid.
TEST_DATETIME=$(date -v+1M -v-1M -jf "%F %T" "$START_DATETIME" +"%F %T" 2> /dev/null)
if [[ "$TEST_DATETIME" != "$START_DATETIME" ]]
then
report_error_and_exit "'$START_DATETIME' is not a valid date/time value. $OSTYPE"
fi
fi
echo "The date/time is valid."
I tested this script on a Red Hat-based system, a Debian-based system and OSX, and it worked as expected on all three platforms. I did not have time to test on Windows (Cygwin).
For validation of YYYY-MM-DD (ISO 8601) dates on OSX in the BASH shell, the following approach validates both the format and the date.
isYYYYMMDDdate() {
[[ "$1" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] && [[ "$1" == $(date -r $(date -j -f "%Y-%m-%d" "$1" "+%s") '+%Y-%m-%d') ]] &> /dev/null; echo "$?"
}
It first uses a regular expression match to check the format.
Then, it converts the date to epoch time and then back to a date.
If the original and twice-converted dates match, then it is valid.
Test a valid date: 2005-11-30
$ isYYYYMMDDdate 2005-11-30
0
Test an invalid date: 2005-11-31
$ isYYYYMMDDdate 2005-11-31
1
Test a valid date formatted incorrectly: 1979-20-04
$ isYYYYMMDDdate 1979-20-04
1
The date command will parse a date given with the -d argument. If the date is invalid, an error message is printed to STDERR and date exits with an error status. If the date is valid, it prints the date on STDOUT and exits with a success status.
Because of this, date -d "$date" can be used directly in a bash if statement.
The first wrinkle is that to prevent printing a message for valid dates, you need to redirect STDOUT to /dev/null using >/dev/null.
The second wrinkle is that date accepts an empty string as a valid date without complaint. In most cases, that should mean that your user didn't enter a date when they should have. You will want to test for an empty date separately using the test [ "z$date" != "z" ]
date also accepts a variety of formats. If you are using actual bash (as opposed to dash or some of ther sh variety, you could use regular expressions against your preferred format in place of a simple check for an empty string. For example to check my preferred ISO format, I would use: [[ "$date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]
date=2001-01-01
if [ "z$date" != "z" ] && date -d "$date" >/dev/null
then
echo "VALID DATE"
fi
If you try this with an invalid date (such as 2001-01-53), it doesn't get into the if and it prints out:
date: invalid date ‘2001-01-53’
Alternately, you could check if the date is invalid and exit:
date=2001-01-01
if [ "z$date" == "z" ]
then
echo "No date specified"
exit 1
fi
if ! [[ "$date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]
then
echo "Expected date in YYYY-MM-DD format"
exit 1
fi
if ! date -d "$date" >/dev/null
then
exit 1
fi
echo "VALID DATE"
case statements make it easy to support multiple formats and capturing date-parts, i.e.
case ${date} in
[0-3][0-9]-[0-1][0-9]-[0-9][0-9][0-9][0-9] )
yr=...
mn=...
dy=...
;;
[0-1][0-9]-[0-3][0-9]-[0-9][0-9][0-9][0-9] )
yr=...
dy=...
mn=...
;;
.... other formats
;;
* )
echo "ERROR on date format, from value=$date, expected formats ..."
return 1
;;
esac
I hope this helps.
You can use the strptime() function available in Python's time or datetime modules or Perl's Time::Piece module.

How can I find and delete files based on date in a linux shell script without find?

PLEASE NOTE THAT I CANNOT USE 'find' IN THE TARGET ENVIRONMENT
I need to delete all files more than 7 days old in a linux shell script. SOmething like:
FILES=./path/to/dir
for f in $FILES
do
echo "Processing $f file..."
# take action on each file. $f store current file name
# perhaps stat each file to get the last modified date and then delete files with date older than today -7 days.
done
Can I use 'stat' to do this? I was trying to use
find *.gz -mtime +7 -delete
but discovered that I cannot use find on the target system (there is no permission for the cron user and this can't be changed). Target system is Redhat Enterprise.
The file names are formatted like this:
gzip > /mnt/target03/rest-of-path/web/backups/DATABASENAME_date "+%Y-%m-%d".gz
This should work:
#!/bin/sh
DIR="/path/to/your/files"
now=$(date +%s)
DAYS=30
for file in "$DIR/"*
do
if [ $(((`stat $file -c '%Y'`) + (86400 * $DAYS))) -lt $now ]
then
# process / rm / whatever the file...
fi
done
A bit of explanation: stat <file> -c '%Z' gives the modification time of the file as seconds since the UNIX epoch for a file, and $(date +%s) gives the current UNIX timestamp. Then there's just a simple check to see whether the file's timestamp, plus seven days' worth of seconds, is greater than the current timestamp.
Since you have time in the filename then use that to time the deletion heres some code that does that :
This script gets the current time in seconds since epoch and then calculates the timestamp 7 days ago. Then for each file parses the filename and converts the date embeded in each filename to a timestamp then compares timestamps to determine which files to delete. Using timestamps gets rid of all hassles with working with dates directly (leap year, different days in months, etc )
The actual remove is commented out so you can test the code.
#funciton to get timestamp X days prior to input timestamp
# arg1 = number of days past input timestamp
# arg2 = timestamp ( e.g. 1324505111 ) seconds past epoch
getTimestampDaysInPast () {
daysinpast=$1
seconds=$2
while [ $daysinpast -gt 0 ] ; do
daysinpast=`expr $daysinpast - 1`
seconds=`expr $seconds - 86400`
done
# make midnight
mod=`expr $seconds % 86400`
seconds=`expr $seconds - $mod`
echo $seconds
}
# get current time in seconds since epoch
getCurrentTime() {
echo `date +"%s"`
}
# parse format and convert time to timestamp
# e.g. 2011-12-23 -> 1324505111
# arg1 = filename with date string in format %Y-%m-%d
getFileTimestamp () {
filename=$1
date=`echo $filename | sed "s/[^0-9\-]*\([0-9\-]*\).*/\1/g"`
ts=`date -d $date | date +"%s"`
echo $ts
}
########################### MAIN ############################
# Expect directory where files are to be deleted to be first
# arg on commandline. If not provided then use current working
# directory
FILEDIR=`pwd`
if [ $# -gt 0 ] ; then
FILEDIR=$1
fi
cd $FILEDIR
now=`getCurrentTime`
mustBeBefore=`getTimestampDaysInPast 7 $now`
SAVEIFS=$IFS
# need this to loop around spaces with filenames
IFS=$(echo -en "\n\b")
# for safety change this glob to something more restrictive
for f in * ; do
filetime=`getFileTimestamp $f`
echo "$filetime lt $mustBeBefore"
if [ $filetime -lt $mustBeBefore ] ; then
# uncomment this when you have tested this on your system
echo "rm -f $f"
fi
done
# only need this if you are going to be doing something else
IFS=$SAVEIFS
If you prefer to rely on the date in the filenames, you can use this routine, that checks if a date is older than another:
is_older(){
local dtcmp=`date -d "$1" +%Y%m%d`; shift
local today=`date -d "$*" +%Y%m%d`
return `test $((today - dtcmp)) -gt 0`
}
and then you can loop through filenames, passing '-7 days' as the second date:
for filename in *;
do
dt_file=`echo $filename | grep -o -E '[12][0-9]{3}(-[0-9]{2}){2}'`
if is_older "$dt_file" -7 days; then
# rm $filename or whatever
fi
done
In is_older routine, date -d "-7 days" +%Y%m%d will return the date of 7 days before, in numeric format ready for the comparison.
DIR=''
now=$(date +%s)
for file in "$DIR/"*
do
echo $(($(stat "$file" -c '%Z') + $((86400 * 7))))
echo "----------"
echo $now
done

Resources