404

One of the arguments that my script receives is a date in the following format: yyyymmdd.

I want to check if I get a valid date as an input.

How can I do this? I am trying to use a regex like: [0-9]\{\8}

2
  • Checking if the format is right is easy. But i don't think that you can, in bash (with built-ins), check if the date is valid. Commented Jan 14, 2014 at 11:59
  • This question is similar to: How can I match a string with a regex in Bash?. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem. Commented Jul 29, 2024 at 18:27

5 Answers 5

624

You can use the test construct, [[ ]], along with the regular expression match operator, =~, to check if a string matches a regex pattern (documentation).

For your specific case, you can write:

[[ "$date" =~ ^[0-9]{8}$ ]] && echo "yes"

Or more a accurate test:

[[ "$date" =~ ^[0-9]{4}(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])$ ]] && echo "yes"
#             |\______/\______*______/\______*__________*______/|
#             |   |           |                  |              |
#             |   |           |                  |              |
#             | --year--   --month--           --day--          |
#             |          either 01...09      either 01..09      |
#      start of line         or 10,11,12         or 10..29      |
#                                                or 30, 31      |
#                                                          end of line

That is, you can define a regex in Bash matching the format you want. This way you can do:

[[ "$date" =~ ^regex$ ]] && echo "matched" || echo "did not match"

where commands after && are executed if the test is successful, and commands after || are executed if the test is unsuccessful.

Note this is based on the solution by Aleks-Daniel Jakimenko in User input date format verification in bash.


In other shells you can use grep. If your shell is POSIX compliant, do

(echo "$date" | grep -Eq  ^regex$) && echo "matched" || echo "did not match"

In fish, which is not POSIX-compliant, you can do

echo "$date" | grep -Eq "^regex\$"; and echo "matched"; or echo "did not match"

Caveat: These portable grep solutions are not water-proof! For example, they can be tricked by input parameters that contain newlines. The first mentioned bash-specific regex check does not have this issue.

Sign up to request clarification or add additional context in comments.

19 Comments

@Aleks-DanielJakimenko using grep seems to be the best option if you’re using sh, fish or other less equipped shells.
@tomekwi, yes, of course. But this question is tagged bash, so it is better to give an answer that makes sense in bash. You can always list alternative sh-compatible solutions, but in no way it should be the main answer.
@fedorqui This regular expression allows month or/and day to be zero. For example these invalid dates are considered as valid by the code: 20160015 20161200 . I think the following patter is more accurate ^[0-9]{4}(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])$
Easy to understand code is always preferable to "good" code. This has been studied at length.
Caveat: the solution using grep accepts multi-line values where each line contains a date: e.g. printf '%s\n%s\n' "$date" "$date" | grep … will be accepted which might not be expected.
|
91

In bash version 3 you can use the '=~' operator:

if [[ "$date" =~ ^[0-9]{8}$ ]]; then
    echo "Valid date"
else
    echo "Invalid date"
fi

Reference: http://tldp.org/LDP/abs/html/bashver3.html#REGEXMATCHREF

NOTE: The quoting in the matching operator within the double brackets, [[ ]], is no longer necessary as of Bash version 3.2

2 Comments

This doesn't work. if ! [[ "hello world" =~ 'hello [a-z]+' ]]; then echo "doesn't work"; f prints "doesn't work". I tried with double quotes as well and that didn't help. Removing the quotes throws a syntax error (which is expected since there's a space).
Try this: if [[ "hello world" =~ hello\ [a-z]+ ]]; then echo "does work"; fi Prints "does work".
52

A good way to test if a string is a correct date is to use the command date:

if date -d "${DATE}" >/dev/null 2>&1
then
  # do what you need to do with your date
else
  echo "${DATE} incorrect date" >&2
  exit 1
fi

from comment: one can use formatting

if [ "2017-01-14" == $(date -d "2017-01-14" '+%Y-%m-%d') ] 

7 Comments

Highly rate your answer as it lets the date function deal with the dates and not the error-prone regexs'
This is good for checking on broad date options, but if you need to verify a specific date format, can it do that? For example if i do date -d 2017-11-14e it returns Tue Nov 14 05:00:00 UTC 2017, but that would break my script.
You could use something like that : if [ "2017-01-14" == $(date -d "2017-01-14" '+%Y-%m-%d') ] It tests if the date is correct and check if the result is the same as your entered data. By the way, be very careful with localized date format (Month-Day-Year vs. Day-Month-Year for instance)
Might not work, depending on your locale. American-formatted dates using MM-DD-YYYY won't work anywhere else in the world, using either DD-MM-YYYY (Europe) or YYYY-MM-DD (some places in Asia)
@Paul, what may not work? As written in a comment, one can use formatting options...
|
7

In addition to other answers of the =~ Bash operator - Extended Regular Expressions (ERE).

This is the syntax used by awk and egrep (or grep -E),
as well as by Bash's [[ ... =~ ... ]] operator.

For example, a function which supports multiple test provided in multiple arguments:

#!/bin/bash

#-----------#
# Functions #
#-----------#

function RT
{
    declare __line;

    for __line in "${@:2}";
    do
        if ! [[ "$__line" =~ $1 ]];
        then
            return 1;
        fi
    done

    return 0;
}

#-----------#
# Main      #
#-----------#

regex_v='^[0-9]*$';
value_1_v='12345';
value_2_v='67890';

if RT "$regex_v" "$value_1_v" "$value_2_v";
then
    printf 'Valid';
else
    printf 'Invalid';
fi

Description

Function RT or Regex Test

# Declare a local variable for a loop.

declare __line;
# Loop for every argument's value except the first - regex rule

for __line in "${@:2}";
# Test the value and return a **non-zero** return code if failed.
# Alternative: if [[ ! "$__line" =~ $1 ]];

if ! [[ "$__line" =~ $1 ]];
# Return a **zero** return code - success.

return 0;

Main code

# Define arguments for the function to test

regex_v='^[0-9]*$'; # Regex rule
value_1_v='12345'; # First value
value_2_v='67890'; # Second value
# A statement which runs the function with specified arguments
# and executes `printf 'Valid';` if succeeded, else - `printf 'Invalid';`

if RT "$regex_v" "$value_v";

It should be possible to point at failed argument, for example, by appending a counter to the loop and printing its value to stderr.

Related

The quotes around the right-hand side of the =~ operator cause it to become a string, rather than a RegularExpression.

Source

Comments

2

Where the usage of a regex can be helpful to determine if the character sequence of a date is correct, it cannot be used easily to determine if the date is valid. The following examples will pass the regular expression, but are all invalid dates: 20180231, 20190229, 20190431

So if you want to validate if your date string (let's call it datestr) is in the correct format, it is best to parse it with date and ask date to convert the string to the correct format. If both strings are identical, you have a valid format and valid date.

if [[ "$datestr" == $(date -d "$datestr" "+%Y%m%d" 2>/dev/null) ]]; then
     echo "Valid date"
else
     echo "Invalid date"
fi

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.