validate date in unix for DD-MM-YYYY format - bash

How to validate the date in Unix.
Where the date format is DD-MM-YYYY.
For example if i run one script ./ValidateDate.sh DD-MM-YYYY then it should check that the date is in this format and also that the date exists.
If date is valid and in above format then proceed for next else print message 'Invalid date'
Thanks.

Well... This is a fine can o' worms.
Any shell script that you create may not work on all of the various flavors of Unix/Linux.
The BSD date command (found on OS X and Solaris) does a great job at taking in the date and verifying the format. It requires you to specify your date format, but once you do, it has no problems:
if date -j -f "%d-%m-%Y" "$DATE" 2>&1 > /dev/null # No output
then
echo "ERROR: Date '$DATE' is not a valid format."
exit 2
else
echo "Date is '$DATE'."
fi
Linux and other systems that use GNU's date can also validate the date, but use a different syntax:
date -d "$DATE" 2>&1 /dev/null # With a bit of luck this will work...
if $? -ne 0
then
echo "ERROR: Date '$DATE' is not a valid format."
else
echo "Date is '$DATE'."
fi
I say With a bit of luck because it's up to the date command to figure out your date's format. This normally works, and will work with your YYYY-MM-DD format, but it can be confusing:
Imagine this:
$ DATE="15/6/2014" # It's June 15, 2014
$ date -d "$DATE"
date: invalid date `15/6/2014' # Invalid?
That's because in my timezone, the dd/mm/yyyy format isn't a valid format. In my timezone, the date should be mm/dd/yyyy. To get around this you can use Wintermute's suggestion and format the date into ISO format before using GNU's date format.
A more universal possibility is to use Perl or Python to figure out if the date is correct:
if perl -mTime::Piece -e "Time::Piece->strptime(\"$DATE\", \"%Y-%m-%d\")" 2> /dev/null
then
echo "ERROR: Date '$DATE' is not a valid format."
else
echo "Date is '$DATE'."
fi
This Perl oneliner will return a non-zero error status if $DATE isn't a valid date in %Y-%m-%d format.

You can validate the date with the date utility, but you first have to transform the input into something it can understand. I suggest ISO representation (always). It could, for example, look like this:
#!/bin/sh
PATTERN='\([0-9]\{1,2\}\)-\([0-9]\{1,2\}\)-\([0-9]\+\)'
# if the pattern doesn't match, this will give date an empty string, so it will fail as expected.
if date -d $(echo "$1" | sed -n "/$PATTERN/ { s/$PATTERN/\3-\2-\1/; p }") > /dev/null 2>&1 ; then
echo valid
else
echo invalid
fi

awk way
awk 'split($0,a,"-"){print strftime("%d-%m-%Y",mktime(a[3]" "a[2]" "a[1]" 00 00 00"))==$0?"valid":"not valid"}' <<< "31-12-1992"
It Converts the string to epoch,converts epoch back then checks against the original.
Edit:
Thought i would add after testing this works for dates
FROM 01-01-0
TO 31-12-2147483647
Although a drawback is after you go below the year 1000 you have to remove leading zeros from the year.

You can do this fairly easily if you are willing to break the validation into two steps. First, check that the string is in the right format:
date=$1
[[ $date =~ ([0-9][0-9])-([0-9][0-9])-([0-9]+) ]] || { printf "Invalid date format\n"; exit 1; }
If that test passes, you can extract the day, month, and year fields, then verify that each falls in the correct range.
day=${BASH_REMATCH[1]}
month=${BASH_REMATCH[2]}
year=${BASH_REMATCH[3]}
thirty_one_days='0[1-9]|[12][0--9]|3[01]'
thirty_days='0[1-9]|[12][0--9]|30'
twenty_eight_days='0[1-9]|1[0-9]|2[0-8]'
case $month in
01|03|05|07|08|10|12)
[[ $day =~ $thirty_one_days ]] ;;
04|06|09|11)
[[ $day =~ $thirty_days ]] ;;
02)
# 1-28, but 29 OK in a leap year.
[[ $day =~ $twenty_eight_days ]] ||
(( year % 4 == 0 && $year % 400 == 0 && day == 29 ))
*)
false
esac || { print "Invalid date\n"; exit 1; }

Related

Validate user input date as parameter - bash script

Am trying to validate that the first (and only) parameter a user passes into a script is a valid date in the format dd/mm/yyyy e.g. 13/01/2022.
I started off with a regex which was fine but doesn't validate the date. I found the suggestion (on stack overflow) to use date -d "$1" '<date format>' and then use that to move forwards or error.
I can get this to validate a YYYY-MM-DD date but DD-MM-YYYY or my preferred DD/MM/YYYY always throw an invalid date.
I have hardcoded this to today's date in the code example below and have been changing to date format string.
I can get date '+%d/%m/%Y' on the command line to return today's date in the format I want. Is there a limitation on the format I can validate?
This throws Invalid date for 02/12/2022 (today's date of posting).
#datestr=$1
datestr=$(date '+%d/%m/%Y')
echo $datestr
if [[ "$datestr" == $(date -d "$datestr" '+%d/%m/%Y') ]]; then
echo "Valid date"
else
echo "Invalid date"
fi
TIA
[Edit - my starting point for the solution]
Check if a string matches a regex in Bash script
Using GNU date and bash:
#!/bin/bash
datestr=$1
if [[ $datestr = [0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9] ]] &&
date -d "${datestr:6}-${datestr:3:2}-${datestr:0:2}" &>/dev/null
then
echo "Valid date"
else
echo "Invalid date"
fi

Compare two dates in Bash in MAC OSX

I'm new to Bash, apologies in advance.
Set-up
I have a particular end date end which depends on a particular starting date s and a period length p such that, end=s+p.
Problem
I want to execute a command if and only if today's date is before or equal to the end date.
That is: execute command iff date ≤ end.
Code
s='20/09/2017'
p='1'
end=$( date -j -v +"$p"d -f "%d/%m/%Y" "$s")
if [[ date < “$end” ]];
then
echo 'ok'
fi
There are 2 things not the way they should be,
p=1 implies end = '21/09/2017' < date = '26/09/2017', but still I get an ok.
I use date < “$end” but I want date ≤ “$end”
How to correct 1 and 2?
When you format the date like YYYYMMDD you can simply use an alphanumeric comparison:
start=$(date +'%Y%m%d')
end=$(date +'%Y%m%d')
# -gt means greater than - for example.
# check `help test` to get all available operators
if [ $start -gt $end ] ; then ...
First you need to use a date format which is lexicographical, meaning the big units come first. I highly recommend the ISO standard YYYY-MM-DD or YYYYMMDD. Second, <= can be written as -le (less or equal).
Your:
end=$( date -j -v+"$p"d -f "%d/%m/%Y" "$s" )
does not produce what you expect:
echo "$end"
Thu Sep 21 14:40:54 CEST 2017
What you expect can be obtained with:
end=$( date -j -v+"$p"d -f "%d/%m/%Y" "$s" +"%d/%m/%Y" )
Your comparison:
if [[ date < “$end” ]]; then
does not behave as you think. date is not expanded as the result of the invocation of the date command. So, your comparison is a string comparison (< in the [[.]] conditional expression context) between string "date" and string "Thu Sep 21 14:40:54 CEST 2017". You should use something like:
date=$( date )
if [[ "$date" < “$end” ]]; then
instead (but using date as a variable name is probably not a very good idea).
As noticed by the other answers, the date format you chose is not the best for comparisons. So, combining the various fixes with the other wise suggestions, you could try something like:
s='20170920'
p='1'
end=$( date -j -v+"$p"d -f "%Y%m%d" "$s" +"%Y%m%d" )
now=$( date +"%Y%m%d" )
if (( now <= end )); then
echo 'ok'
fi
Note: if (( now <= end )); then is an arithmetic comparison and it should work in this specific case. Note also that it uses now and end, not $now and $end: the ((.)) arithmetic comparison interprets variable names as the integer value stored in the variable. Anyway, using dates, even in YYYYmmdd format, as if they were integers is not that clean. Using UNIX timestamps, which should be OK at least until year 2038, is probably cleaner:
s='20170920'
p='1'
end=$( date -j -v+"$p"d -f "%Y%m%d" "$s" +"%s" )
now=$( date +"%s" )
if (( now <= end )); then
echo 'ok'
fi
because here now and end are number of seconds since the Epoch, that is, true integers. Note, however, that the result could be different from that of the previous solution because you have now a one-second accuracy instead of one-day. Chose which one corresponds to your needs.

Day of month not an integer

I am making an adjustment a bash script of mine to output the ordinal part (st, nd, rd, th) of a day of month. I am trying to adjust it to be used within a date pattern in the date command.
Here is a simple test to determine if day of month from date command is an int:
dayOfMonth=$(date +"%d") && [[ ${dayOfMonth} =~ ^[0-9]+$ ]] && echo ${dayOfMonth} is an int || echo ${dayOfMonth} is NOT an int
Output is as I expect
01 is an int
Now I put that code into a script:
#!/bin/bash
[[ ${1} =~ ^[0-9]+$ ]] && echo ${1} is an int || echo ${1} is NOT an int
Seems OK:
dateTest.sh 12
12 is an int
But when I try to use it within a date command, the script is giving me conflicting output. echo sees 12 but the test is seeing %2
date --date='06/12/2012 07:21:22' +"`dateTest.sh %d`"
12 is NOT an int
To clarify my purpose, the end result of the script will be something like this:
#!/bin/bash
n=$(date +"%d")
if [ $# -ge 1 ] ; then
n=$1
if ! [[ $n =~ ^[0-9]+$ ]] ; then
echo Arg dateInteger [$dateInteger] must be an integer.
exit 1
fi
fi
if [ $n -ge 11 -a $n -le 13 ] ; then
echo "th"
else
case $(( $n%10 )) in
1)
echo st
;;
2)
echo nd
;;
3)
echo rd
;;
*)
echo th
;;
esac
fi
So that I can it like this:
date --date='06/12/2012 07:21:22' +"%A, %d`dateTest.sh %d` of %B %Y, %I:%M:%S %p"
which would output
Tuesday, 12th of June 2012, 07:21:22 AM
Finished script re-write. Thanks to input from #GordonDavisson, I completed my script re-write: http://pastebin.com/xZ1afqqC. Now it either outputs just the ordinal from an integer, or will output a fully formatted date where you can use standard format strings from date with the addition of "%O" for the ordinal.
You're doing things in the wrong order -- you have to turn "%d" into an integer (the day of the month) before passing it to your script, not after. Consider the command:
date --date='06/12/2012 07:21:22' +"`dateTest.sh %d`"
What this does is run dateTest.sh %d, i.e. it passes "%d" as the argument to your script. The script naturally outputs "%d is not an int". This is then used as the format string for the date command, i.e. date --date='06/12/2012 07:21:22' +"%d is not an int". The date command replaces the "%d" part with the day number, and leaves the rest alone, giving "12 is not an int".
In order to make this work, you have to get the day number first, then pass that to your script. Something like this:
dateTest.sh "$(date --date='06/12/2012 07:21:22' +"%d")"
Unfortunately, your end result script also wants a bunch of other date formatting done that can't be passed to the dateTest script. I think in that case it'd be best to do it in stages:
dayWithSuffix="$(dateTest.sh "$(date --date='06/12/2012 07:21:22' +"%d")")"
date --date='06/12/2012 07:21:22' +"%A, $dayWithSuffix of %B %Y, %I:%M:%S %p"
BTW, several general scripting suggestions:
Send error/debug output to stderr, not stdout, so it doesn't get confused with the script's regular output (part of the problem here). For example, echo "${1} is NOT an int" >&2
Speaking of which, put strings that contain variables in double-quotes (as I did in that last example) to avoid weird misparsing of whitespace, wildcards, etc. Your result script, for example, contains echo Arg dateInteger [$dateInteger] must be an integer. -- you probably don't realize that under certain circumstances the [$dateInteger] part will be replaced by a list of filenames.
Finally, use $( ... ) instead of backquotes. In most cases they're equivalent, but the parenthesis version is easier to read and avoids some weird parsing oddities of the contents. Notice how I nested two such expressions in the assignment to dayWithSuffix? That's much trickier to get right with backquotes.

Test if variable is a date with the correct format [duplicate]

This question already has an answer here:
Bash shell: How to check for specific date format?
(1 answer)
Closed 1 year ago.
I would like to test if a variable is a date (not a problem), but if the variable have the correct date format (yyyy-MM-dd).
I tried :
export DATE_REFRESH=01/01/1900
if ! date -d $DATE_REFRESH "+%Y-%m-%d"; then
echo "$DATE_REFRESH is not a valid date. Expected format is : yyyy-MM-dd"
fi
But it doesn't work.
I can try with this :
if [[ $DATE_REFRESH == [0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9] ]]
But I don't want to have a date to 33/19/2000...
You can use this snippet:
isValidDate() {
if [[ "$1" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] && date -d "$1">/dev/null 2>&1; then
echo "valid"
else
echo "invalid"
fi;
}
Testing:
isValidDate "1900-12-25"
valid
isValidDate "1900-14-25"
invalid
isValidDate "01/01/1900"
invalid
You have to do both tests because the date string can be everything, see the manpage:
The --date=STRING is a mostly free format human readable date
string such as ...
Use that:
if ! [[ "$DATE_REFRESH" =~ ^[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}$ ]] \
|| ! date -d $DATE_REFRESH >/dev/null 2>&1; then
echo "$DATE_REFRESH is not a valid date. Expected format is : yyyy-MM-dd"
fi

user input date format verification in bash

So I'm trying to write a simple script in bash that asks user for input date in
following format (YYYY-dd-mm). Unfortunately I got stuck on first step, which is verifying that input is in correct format. I tried using 'date' with no luck (as it returns actual current date). I'm trying to make this as simple as possible. Thank you for your help!
Using regex:
if [[ $date =~ ^[0-9]{4}-[0-3][0-9]-[0-1][0-9]$ ]]; then
or with bash globs:
if [[ $date == [0-9][0-9][0-9][0-9]-[0-3][0-9]-[0-1][0-9] ]]; then
Please note that this regex will accept a date like 9999-00-19 which is not a correct date. So after you check its possible correctness with this regex you should verify that the numbers are correct.
IFS='-' read -r year day month <<< "$date"
This will put the numbers into $year $day and $month variables.
date -d "$date" +%Y-%m-%d
The latter is the format, the -d allows an input date. If it's wrong it will return an error that can be piped to the bit bucket, if it's correct it will return the date.
The format modifiers can be found in the manpage of date man 1 date. Here an example with an array of 3 dates:
dates=(2012-01-34 2014-01-01 2015-12-24)
for Date in ${dates[#]} ; do
if [ -z "$(date -d $Date 2>/dev/null)" ; then
echo "Date $Date is invalid"
else
echo "Date $Date is valid"
fi
done
Just a note of caution: typing man date into Google while at work can produce some NSFW results ;)

Resources