Home > Blockchain >  Validate user input date as parameter - bash script
Validate user input date as parameter - bash script

Time:12-03

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

CodePudding user response:

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

CodePudding user response:

Here's a pure bash function that does the full validation of a date in the format %d/%m/%Y:

#!/bin/bash

date_is_dmY() {
    [[ $1 =~ ^([0-9]{2})/([0-9]{2})/([0-9]{4})$ ]] || return 1

    local year month day
    ((
        year  = 10#${BASH_REMATCH[3]},
        month = 10#${BASH_REMATCH[2]},
        day   = 10#${BASH_REMATCH[1]}
    ))

    (( 1 <= day )) || return 1
    case $month in
    (              2) (( day <= ((year@0 == 0 || (year%4 == 0 && year0 != 0)) ? 29 : 28) )) || return 1 ;;
    (       4|6|9|11) (( day <= 30 )) || return 1 ;;
    (1|3|5|7|8|10|12) (( day <= 31 )) || return 1 ;;
    (              *) return 1 ;;
    esac
}

Here's an example on how to use it:

for datestr in 31/04/2022 29/02/2000 29/02/1900
do
    if date_is_dmY "$datestr"
    then
        printf 'valid date: %s\n' "$datestr"
    else
        printf 'invalid date: %s\n' "$datestr" >&2
    fi
done
invalid date: 31/04/2022
valid date: 29/02/2000
invalid date: 29/02/1900

Pros:

  • It works with any bash on any OS
  • It doesn't fork a date command, so it should be faster (at least when used inside a loop).

Cons:

  • It only supports a specific date format
  • Related