Regex date format validation on Java

Viewed 262616

I'm just wondering if there is a way (maybe with regex) to validate that an input on a Java desktop app is exactly a string formatted as: "YYYY-MM-DD".

11 Answers

Use the following regular expression:

^\d{4}-\d{2}-\d{2}$

as in

if (str.matches("\\d{4}-\\d{2}-\\d{2}")) {
    ...
}

With the matches method, the anchors ^ and $ (beginning and end of string, respectively) are present implicitly.

The pattern above checks conformance with the general “shape” of a date, but it will accept more invalid than valid dates. You may be surprised to learn that checking for valid dates — including leap years! — is possible using a regular expression, but not advisable. Borrowing from an answer elsewhere by Kuldeep, we can all find amusement and admiration for persistence in

((18|19|20)[0-9]{2}[\-.](0[13578]|1[02])[\-.](0[1-9]|[12][0-9]|3[01]))|(18|19|20)[0-9]{2}[\-.](0[469]|11)[\-.](0[1-9]|[12][0-9]|30)|(18|19|20)[0-9]{2}[\-.](02)[\-.](0[1-9]|1[0-9]|2[0-8])|(((18|19|20)(04|08|[2468][048]|[13579][26]))|2000)[\-.](02)[\-.]29

In a production context, your colleagues will appreciate a more straightforward implementation. Remember, the first rule of optimization is Don’t!

You need more than a regex, for example "9999-99-00" isn't a valid date. There's a SimpleDateFormat class that's built to do this. More heavyweight, but more comprehensive.

e.g.

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

boolean isValidDate(string input) {
     try {
          format.parse(input);
          return true;
     }
     catch(ParseException e){
          return false;
     }
}

Unfortunately, SimpleDateFormat is both heavyweight and not thread-safe.

Putting it all together:

  • REGEX doesn't validate values (like "2010-19-19")
  • SimpleDateFormat does not check format ("2010-1-2", "1-0002-003" are accepted)

it's necessary to use both to validate format and value:

public static boolean isValid(String text) {
    if (text == null || !text.matches("\\d{4}-[01]\\d-[0-3]\\d"))
        return false;
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
    df.setLenient(false);
    try {
        df.parse(text);
        return true;
    } catch (ParseException ex) {
        return false;
    }
}



A ThreadLocal can be used to avoid the creation of a new SimpleDateFormat for each call.
It is needed in a multithread context since the SimpleDateFormat is not thread safe:

private static final ThreadLocal<SimpleDateFormat> format = new ThreadLocal<SimpleDateFormat>() {
    @Override
    protected SimpleDateFormat initialValue() {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        df.setLenient(false);
        System.out.println("created");
        return df;
    }
};

public static boolean isValid(String text) {
    if (text == null || !text.matches("\\d{4}-[01]\\d-[0-3]\\d"))
        return false;
    try {
        format.get().parse(text);
        return true;
    } catch (ParseException ex) {
        return false;
    }
}

(same can be done for a Matcher, that also is not thread safe)

The following regex will accept YYYY-MM-DD (within the range 1600-2999 year) formatted dates taking into consideration leap years:

 ^((?:(?:1[6-9]|2[0-9])\d{2})(-)(?:(?:(?:0[13578]|1[02])(-)31)|((0[1,3-9]|1[0-2])(-)(29|30))))$|^(?:(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))(-)02(-)29)$|^(?:(?:1[6-9]|2[0-9])\d{2})(-)(?:(?:0[1-9])|(?:1[0-2]))(-)(?:0[1-9]|1\d|2[0-8])$

Examples:

Date regex examples

You can test it here.

Note: if you want to accept one digit as month or day you can use:

 ^((?:(?:1[6-9]|2[0-9])\d{2})(-)(?:(?:(?:0?[13578]|1[02])(-)31)|((0?[1,3-9]|1[0-2])(-)(29|30))))$|^(?:(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))(-)0?2(-)29)$|^(?:(?:1[6-9]|2[0-9])\d{2})(-)(?:(?:0?[1-9])|(?:1[0-2]))(-)(?:0?[1-9]|1\d|2[0-8])$

I have created the above regex starting from this solution

Construct a SimpleDateFormat with the mask, and then call: SimpleDateFormat.parse(String s, ParsePosition p)

For fine control, consider an InputVerifier using the SimpleDateFormat("YYYY-MM-dd") suggested by Steve B.

Below added code is working for me if you are using pattern dd-MM-yyyy.

public boolean isValidDate(String date) {
        boolean check;
        String date1 = "^(0?[1-9]|[12][0-9]|3[01])-(0?[1-9]|1[012])-([12][0-9]{3})$";
        check = date.matches(date1);

        return check;
    }

If you want a simple regex then it won't be accurate. https://www.freeformatter.com/java-regex-tester.html#ad-output offers a tool to test your Java regex. Also, at the bottom you can find some suggested regexes for validating a date.

ISO date format (yyyy-mm-dd):

^[0-9]{4}-(((0[13578]|(10|12))-(0[1-9]|[1-2][0-9]|3[0-1]))|(02-(0[1-9]|[1-2][0-9]))|((0[469]|11)-(0[1-9]|[1-2][0-9]|30)))$

ISO date format (yyyy-mm-dd) with separators '-' or '/' or '.' or ' '. Forces usage of same separator accross date.

^[0-9]{4}([- /.])(((0[13578]|(10|12))\1(0[1-9]|[1-2][0-9]|3[0-1]))|(02\1(0[1-9]|[1-2][0-9]))|((0[469]|11)\1(0[1-9]|[1-2][0-9]|30)))$

United States date format (mm/dd/yyyy)

^(((0[13578]|(10|12))/(0[1-9]|[1-2][0-9]|3[0-1]))|(02/(0[1-9]|[1-2][0-9]))|((0[469]|11)/(0[1-9]|[1-2][0-9]|30)))/[0-9]{4}$

Hours and minutes, 24 hours format (HH:MM):

^(20|21|22|23|[01]\d|\d)((:[0-5]\d){1,2})$

Good luck

Related