PHP Regex to check date is in YYYY-MM-DD format

PhpRegexDate Format

Php Problem Overview


I'm trying to check that dates entered by end users are in the YYYY-MM-DD. Regex has never been my strong point, I keep getting a false return value for the preg_match() I have setup.

So I'm assuming I have made a mess of the regex, detailed below.

$date="2012-09-12";

if (preg_match("^[0-9]{4}-[0-1][0-9]-[0-3][0-9]$",$date))
	{
		return true;
	}else{
		return false;
	}

Any thoughts?

Php Solutions


Solution 1 - Php

Try this.

$date="2012-09-12";

if (preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/",$date)) {
    return true;
} else {
    return false;
}

Solution 2 - Php

It's probably better to use another mechanism for this.

The modern solution, with DateTime:

$dt = DateTime::createFromFormat("Y-m-d", $date);
return $dt !== false && !array_sum($dt::getLastErrors());

This validates the input too: $dt !== false ensures that the date can be parsed with the specified format and the array_sum trick is a terse way of ensuring that PHP did not do "month shifting" (e.g. consider that January 32 is February 1). See DateTime::getLastErrors() for more information.

Old-school solution with explode and checkdate:

list($y, $m, $d) = array_pad(explode('-', $date, 3), 3, 0);
return ctype_digit("$y$m$d") && checkdate($m, $d, $y);

This validates that the input is a valid date as well. You can do that with a regex of course, but it's going to be more fuss -- and February 29 cannot be validated with a regex at all.

The drawback of this approach is that you have to be very careful to reject all possible "bad" inputs while not emitting a notice under any circumstances. Here's how:

  • explode is limited to return 3 tokens (so that if the input is "1-2-3-4", $d will become "3-4")
  • ctype_digit is used to make sure that the input does not contain any non-numeric characters (apart from the dashes)
  • array_pad is used (with a default value that will cause checkdate to fail) to make sure that enough elements are returned so that if the input is "1-2" list() will not emit a notice

Solution 3 - Php

yyyy-mm-dd : /^((((19|[2-9]\d)\d{2})\-(0[13578]|1[02])\-(0[1-9]|[12]\d|3[01]))|(((19|[2-9]\d)\d{2})\-(0[13456789]|1[012])\-(0[1-9]|[12]\d|30))|(((19|[2-9]\d)\d{2})\-02\-(0[1-9]|1\d|2[0-8]))|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))\-02\-29))$/g

yyyy/mm/dd : /^((((19|[2-9]\d)\d{2})\/(0[13578]|1[02])\/(0[1-9]|[12]\d|3[01]))|(((19|[2-9]\d)\d{2})\/(0[13456789]|1[012])\/(0[1-9]|[12]\d|30))|(((19|[2-9]\d)\d{2})\/02\/(0[1-9]|1\d|2[0-8]))|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))\/02\/29))$/g

mm-dd-yyyy : /^(((0[13578]|1[02])\-(0[1-9]|[12]\d|3[01])\-((19|[2-9]\d)\d{2}))|((0[13456789]|1[012])\-(0[1-9]|[12]\d|30)\-((19|[2-9]\d)\d{2}))|(02\-(0[1-9]|1\d|2[0-8])\-((19|[2-9]\d)\d{2}))|(02\-29\-((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/g

mm/dd/yyyy : /^(((0[13578]|1[02])\/(0[1-9]|[12]\d|3[01])\/((19|[2-9]\d)\d{2}))|((0[13456789]|1[012])\/(0[1-9]|[12]\d|30)\/((19|[2-9]\d)\d{2}))|(02\/(0[1-9]|1\d|2[0-8])\/((19|[2-9]\d)\d{2}))|(02\/29\/((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/g

dd/mm/yyyy : /^(((0[1-9]|[12]\d|3[01])\/(0[13578]|1[02])\/((19|[2-9]\d)\d{2}))|((0[1-9]|[12]\d|30)\/(0[13456789]|1[012])\/((19|[2-9]\d)\d{2}))|((0[1-9]|1\d|2[0-8])\/02\/((19|[2-9]\d)\d{2}))|(29\/02\/((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/g

dd-mm-yyyy : /^(((0[1-9]|[12]\d|3[01])\-(0[13578]|1[02])\-((19|[2-9]\d)\d{2}))|((0[1-9]|[12]\d|30)\-(0[13456789]|1[012])\-((19|[2-9]\d)\d{2}))|((0[1-9]|1\d|2[0-8])\-02\-((19|[2-9]\d)\d{2}))|(29\-02\-((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/g

Solution 4 - Php

Criteria:

Every year divisible by 4 is a leap year, except when it is divisible by 100 unless it is divisible by 400. So:

2004 - leap year - divisible by 4
1900 - not a leap year - divisible by 4, but also divisible by 100
2000 - leap year - divisible by 4, also divisible by 100, but divisible by 400

February has 29 days in a leap year and 28 when not a leap year

30 days in April, June, September and November

31 days in January, March, May, July, August, October and December

Test:

The following dates should all pass validation:

1976-02-29
2000-02-29
2004-02-29
1999-01-31

The following dates should all fail validation:

2015-02-29
2015-04-31
1900-02-29
1999-01-32
2015-02-00

Range:

We'll test for dates from 1st Jan 1000 to 31st Dec 2999. Technically the currently used Gregorian calendar only came into use in 1753 for the British Empire and at various years in the 1600s for countries in Europe, but I'm not going to worry about that.

Regex to test for a leap year:

The years divisible by 400:

1200|1600|2000|2400|2800
can be shortened to:
(1[26]|2[048])00

if you wanted all years from 1AD to 9999 then this would do it:
(0[48]|[13579][26]|[2468][048])00
if you're happy with accepting 0000 as a valid year then it can be shortened:
([13579][26]|[02468][048])00

The years divisible by 4:

[12]\d([02468][048]|[13579][26])

The years divisible by 100:

[12]\d00

Not divisible by 100:

[12]\d([1-9]\d|\d[1-9])

The years divisible by 100 but not by 400:

((1[1345789])|(2[1235679]))00

Divisible by 4 but not by 100:

[12]\d([2468][048]|[13579][26]|0[48])

The leap years:

divisible by 400 or (divisible by 4 and not divisible by 100)
((1[26]|2[048])00)|[12]\d([2468][048]|[13579][26]|0[48])

Not divisible by 4:

[12]\d([02468][1235679]|[13579][01345789])

Not a leap year:

Not divisible by 4 OR is divisible by 100 but not by 400
([12]\d([02468][1235679]|[13579][01345789]))|(((1[1345789])|(2[1235679]))00)

Valid Month and day excluding February(MM-DD):

((01|03|05|07|08|10|12)-(0[1-9]|[12]\d|3[01]))|((04|06|09|11)-(0[1-9]|[12]\d|30))
shortened to:
((0[13578]|1[02])-(0[1-9]|[12]\d|3[01]))|((0[469]|11)-(0[1-9]|[12]\d|30))

February with 28 days:

02-(0[1-9]|1\d|2[0-8])

February with 29 days:

02-(0[1-9]|[12]\d)

Valid date:

(leap year followed by (valid month-day-excluding-february OR 29-day-february)) 
OR
(non leap year followed by (valid month-day-excluding-february OR 28-day-february))

((((1[26]|2[048])00)|[12]\d([2468][048]|[13579][26]|0[48]))-((((0[13578]|1[02])-(0[1-9]|[12]\d|3[01]))|((0[469]|11)-(0[1-9]|[12]\d|30)))|(02-(0[1-9]|[12]\d))))|((([12]\d([02468][1235679]|[13579][01345789]))|((1[1345789]|2[1235679])00))-((((0[13578]|1[02])-(0[1-9]|[12]\d|3[01]))|((0[469]|11)-(0[1-9]|[12]\d|30)))|(02-(0[1-9]|1\d|2[0-8]))))

So there you have it a regex for dates between 1st Jan 1000 and 31st Dec 2999 in YYYY-MM-DD format.

I suspect it can be shortened quite a bit, but I'll leave that up to somebody else.

That will match all valid dates. If you want it to only be valid when it contains just one date and nothing else, then wrap it in ^( )$ like so:

^(((((1[26]|2[048])00)|[12]\d([2468][048]|[13579][26]|0[48]))-((((0[13578]|1[02])-(0[1-9]|[12]\d|3[01]))|((0[469]|11)-(0[1-9]|[12]\d|30)))|(02-(0[1-9]|[12]\d))))|((([12]\d([02468][1235679]|[13579][01345789]))|((1[1345789]|2[1235679])00))-((((0[13578]|1[02])-(0[1-9]|[12]\d|3[01]))|((0[469]|11)-(0[1-9]|[12]\d|30)))|(02-(0[1-9]|1\d|2[0-8])))))$

If you want it for an optional date entry (ie. it can be blank or a valid date) then add ^$| at the beginning, like so:

^$|^(((((1[26]|2[048])00)|[12]\d([2468][048]|[13579][26]|0[48]))-((((0[13578]|1[02])-(0[1-9]|[12]\d|3[01]))|((0[469]|11)-(0[1-9]|[12]\d|30)))|(02-(0[1-9]|[12]\d))))|((([12]\d([02468][1235679]|[13579][01345789]))|((1[1345789]|2[1235679])00))-((((0[13578]|1[02])-(0[1-9]|[12]\d|3[01]))|((0[469]|11)-(0[1-9]|[12]\d|30)))|(02-(0[1-9]|1\d|2[0-8])))))$

Solution 5 - Php

You can make it this way:

if (preg_match("/\d{4}\-\d{2}-\d{2}/", $date)) {
    echo 'true';
} else {
    echo 'false';
}

but you'd better use this one:

$date = DateTime::createFromFormat('Y-m-d', $date);
if ($date) {
    echo $date -> format('Y-m-d');
}

in this case you'll get an object which is muck easier to use than just strings.

Solution 6 - Php

I know that this is a old question. But I think I have a good solution.

$date = "2016-02-21";
$format = "Y-m-d";

if(date($format, strtotime($date)) == date($date)) {
    echo "true";
} else {
    echo "false";
}

You can try it. If you change the date to 21.02.2016 the echo is false. And if you change the format after that to d.m.Y the echo is true.

With this easy code you should be able to check which date-format is used without checking it by the regex.

Maybe there is a person who will test it on every case. But I think my idea is generally valid. For me it seems logical.

Solution 7 - Php

You can use a preg_match with a checkdate php function

$date  = "2012-10-05";
$split = array();
if (preg_match ("/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/", $date, $split))
{
    return checkdate($split[2], $split[3], $split[1]);
}

return false;

Solution 8 - Php

preg_match needs a / or another char as delimiter.

preg_match("/^[0-9]{4}-[0-1][0-9]-[0-3][0-9]$/",$date)

you also should check for validity of that date so you wouldn't end up with something like 9999-19-38

bool checkdate ( int $month , int $day , int $year )

Solution 9 - Php

Probably useful to someone:

$patterns = array(
            'Y'           =>'/^[0-9]{4}$/',
            'Y-m'         =>'/^[0-9]{4}(-|\/)([1-9]|0[1-9]|1[0-2])$/',
            'Y-m-d'       =>'/^[0-9]{4}(-|\/)([1-9]|0[1-9]|1[0-2])(-|\/)([1-9]|0[1-9]|[1-2][0-9]|3[0-1])$/',
            'Y-m-d H'     =>'/^[0-9]{4}(-|\/)([1-9]|0[1-9]|1[0-2])(-|\/)([1-9]|0[1-9]|[1-2][0-9]|3[0-1])\s(0|[0-1][0-9]|2[0-4])$/',
            'Y-m-d H:i'   =>'/^[0-9]{4}(-|\/)([1-9]|0[1-9]|1[0-2])(-|\/)([1-9]|0[1-9]|[1-2][0-9]|3[0-1])\s(0|[0-1][0-9]|2[0-4]):?(0|[0-5][0-9]|60)$/',
            'Y-m-d H:i:s' =>'/^[0-9]{4}(-|\/)([1-9]|0[1-9]|1[0-2])(-|\/)([1-9]|0[1-9]|[1-2][0-9]|3[0-1])\s(0|[0-1][0-9]|2[0-4]):?((0|[0-5][0-9]):?(0|[0-5][0-9])|6000|60:00)$/',
        );
echo preg_match($patterns['Y'], '1996'); // true
echo preg_match($patterns['Y'], '19966'); // false
echo preg_match($patterns['Y'], '199z'); // false
echo preg_match($patterns['Y-m'], '1996-0'); // false
echo preg_match($patterns['Y-m'], '1996-09'); // true
echo preg_match($patterns['Y-m'], '1996-1'); // true
echo preg_match($patterns['Y-m'], '1996/1'); // true
echo preg_match($patterns['Y-m'], '1996/12'); // true
echo preg_match($patterns['Y-m'], '1996/13'); // false
echo preg_match($patterns['Y-m-d'], '1996-11-1'); // true
echo preg_match($patterns['Y-m-d'], '1996-11-0'); // false
echo preg_match($patterns['Y-m-d'], '1996-11-32'); // false
echo preg_match($patterns['Y-m-d H'], '1996-11-31 0'); // true
echo preg_match($patterns['Y-m-d H'], '1996-11-31 00'); // true
echo preg_match($patterns['Y-m-d H'], '1996-11-31 24'); // true
echo preg_match($patterns['Y-m-d H'], '1996-11-31 25'); // false
echo preg_match($patterns['Y-m-d H:i'], '1996-11-31 2400'); // true
echo preg_match($patterns['Y-m-d H:i'], '1996-11-31 24:00'); // true
echo preg_match($patterns['Y-m-d H:i'], '1996-11-31 24:59'); // true
echo preg_match($patterns['Y-m-d H:i'], '1996-11-31 24:60'); // true
echo preg_match($patterns['Y-m-d H:i'], '1996-11-31 24:61'); // false
echo preg_match($patterns['Y-m-d H:i'], '1996-11-31 24:61'); // false
echo preg_match($patterns['Y-m-d H:i:s'], '1996-11-31 24:6000'); // true
echo preg_match($patterns['Y-m-d H:i:s'], '1996-11-31 24:60:00'); // true
echo preg_match($patterns['Y-m-d H:i:s'], '1996-11-31 24:59:59'); // true
echo preg_match($patterns['Y-m-d H:i:s'], '1996-11-31 24:59:60'); // false
echo preg_match($patterns['Y-m-d H:i:s'], '1996-11-31 24:60:01'); // false

Solution 10 - Php

Function to validate generic date format:

function validateDate($date, $format = 'Y-m-d') {
  $d = DateTime::createFromFormat($format, $date);
  return $d && $d->format($format) == $date;
}

Example of execution:

var_dump(validateDate('2021-02-28')); // true
var_dump(validateDate('2021-02-29')); // false

Solution 11 - Php

You could also do it like this:

if (DateTime::createFromFormat('Y-m-d', $date)->format('Y-m-d') === $date) {

    // date is correctly formatted and valid, execute some code

}

This will not only check the format, but also the validity of the date self, since DateTime will create only valid dates and this needs to match the input.

Solution 12 - Php

you can use

function validateDate($date, $format = 'Y-m-d H:i:s')
{
    $d = DateTime::createFromFormat($format, $date);
    return $d && $d->format($format) == $date;
}

$date="2012-09-12";    
echo validateDate($date, 'Y-m-d'); // true or false

Solution 13 - Php

If you want to match that type of date, use:

preg_match("~^\d{4}-\d{2}-\d{2}$~", $date)

Solution 14 - Php

This should tell you if the format is valid and if the input date is valid.

    $datein = '2012-11-0';

    if(preg_match('/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/', $datein)){
        echo 'good';
    }else{
        echo 'no good';
    }

Solution 15 - Php

Check and validate YYYY-MM-DD date in one line statement

function isValidDate($date) {
    return preg_match("/^(\d{4})-(\d{1,2})-(\d{1,2})$/", $date, $m)
        ? checkdate(intval($m[2]), intval($m[3]), intval($m[1]))
        : false;
}

The output will be:

var_dump(isValidDate("2018-01-01")); // bool(true)
var_dump(isValidDate("2018-1-1"));   // bool(true)
var_dump(isValidDate("2018-02-28")); // bool(true)
var_dump(isValidDate("2018-02-30")); // bool(false)

Day and month without leading zero are allowed. If you don't want to allow this, the regexp should be:

"/^(\d{4})-(\d{2})-(\d{2})$/"

Solution 16 - Php

> Format 1 : $format1 = "2012-12-31"; > > Format 2 : $format2 = "31-12-2012";

if (preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/",$format1)) {
    return true;
} else {
    return false;
}

if (preg_match("/^(0[1-9]|[1-2][0-9]|3[0-1])-(0[1-9]|1[0-2])-[0-9]{4}$/",$format2)) {
    return true;
} else {
    return false;
}

Solution 17 - Php

I was searching "how to validate date" and found this solution, Its old but i ll share below method that can be use to validate date in php,

checkdate('01', '31', '2019')

Solution 18 - Php

It all depends on how strict you want this function to be. For instance, if you don't want to allow months above 12 and days above 31 (not depending on the month, that would require writing date-logic), it could become pretty complicated:

function checkDate($date)
{
  $regex = '/^' . 
    '(' .

    // Allows years 0000-9999
    '(?:[0-9]{4})' .
    '\-' .
     
    // Allows 01-12
    '(?:' .
    '(?:01)|(?:02)|(?:03)|(?:04)|(?:05)|(?:06)|(?:07)|(?:08)|(?:09)|(?:10)|' .
    '(?:11)|(?:12)' .
    ')' .
    '\-' .

    // Allows 01-31
    '(?:' .
    '(?:01)|(?:02)|(?:03)|(?:04)|(?:05)|(?:06)|(?:07)|(?:08)|(?:09)|(?:10)|' .
    '(?:11)|(?:12)|(?:13)|(?:14)|(?:15)|(?:16)|(?:17)|(?:18)|(?:19)|(?:20)|' .
    '(?:21)|(?:22)|(?:23)|(?:24)|(?:25)|(?:26)|(?:27)|(?:28)|(?:29)|(?:30)|' .
    '(?:31)' .
    ')' .

    '$/';

  if ( preg_match($regex, $date) ) {
    return true;
  }

  return false;
}

$result = checkDate('2012-09-12');

Personally I'd just go for: /^([0-9]{4}\-([0-9]{2}\-[0-9]{2})$/

Solution 19 - Php

To work with dates in php you should follow the php standard so the given regex will assure that you have valid date that can operate with PHP.

    preg_match("/^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/",$date)

Solution 20 - Php

If it is of any help, here is a regex for j-n-Y format (year has to be greater than 2018):

if (preg_match('/^([1-9]|[1-2][0-9]|[3][0-1])\-([1-9]|[1][0-2])\-(?:20)([1][8-9]|[2-9][0-9])$/', $date)) {
   // Do stuff
}

Solution 21 - Php

From Laravel 5.7 and date format i.e.: 12/31/2019

function checkDateFormat(string $date): bool
{
    return preg_match("/^(0[1-9]|1[0-2])\/(0[1-9]|[1-2][0-9]|3[0-1])\/[0-9]{4}$/", $date);
}

Solution 22 - Php

The below ensures digits YYYY(1900-2099)-MM(01-12)-DD(01-31) as well as utilises checkdate() function for date correctness e.g. 29 Feb:

$date_pattern = '/^(19|20)\d{2}\-(0[1-9]|1[0-2])\-(0[1-9]|[12][0-9]|3[01])$/';

if (empty($date) || (!preg_match("$date_pattern", $date, $m) || (!checkdate($m[2], $m[3], $m[1])))) {
    $echo 'Invalid Start date e.g. YYYY-MM-DD';
}

To change date patterns and year range:

// pattern DD/MM/YYYY where DD (01-31), MM (01-12) and YYYY (1950-2099) & have to be digits - replace checkdate () with !checkdate($m[2], $m[1], $m[3]

// To extend year range e.g. 1900-2099 /^(0[1-9]|[12][0-9]|3[01])/(0[1-9]|1[0-2])/(19|20)\d{2}$/ - replace checkdate () with !checkdate($m[2], $m[1], $m[3]

Solution 23 - Php

This method can be useful to validate date in PHP. Current method is for mm/dd/yyyy format. You have to update parameter sequence in checkdate as per your format and delimiter in explode .

    function isValidDate($dt)
    {
        $dtArr = explode('/', $dt);
        if (!empty($dtArr[0]) && !empty($dtArr[1]) && !empty($dtArr[2])) {
            return checkdate((int) $dtArr[0], (int) $dtArr[1], (int) $dtArr[2]);
        } else {
            return false;
        }
    }

Solution 24 - Php

[If you use Symfony 4.1.2 try this][1]

  $validDate = explode("-",$request->get('date'));
        if (checkdate(filter_var($validDate[1],FILTER_SANITIZE_NUMBER_INT),filter_var($validDate[0],FILTER_SANITIZE_NUMBER_INT),filter_var($validDate[2],FILTER_SANITIZE_NUMBER_INT))){
            $date = date_create(filter_var($request->get('date'),FILTER_SANITIZE_SPECIAL_CHARS));
        }else{
            return $this->redirectToRoute('YOUR_ROUTE');
        }

Solution 25 - Php

A more pragmatic pattern could be like this

$date="2012-09-12";

if (preg_match("/^(20[0-9]{2})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/",$date)) {
    return true;
} else {
    return false;
}

Which will prevent to put 9999-09-12 such values.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncosmicsafariView Question on Stackoverflow
Solution 1 - PhpAvin VargheseView Answer on Stackoverflow
Solution 2 - PhpJonView Answer on Stackoverflow
Solution 3 - PhpShyju KPView Answer on Stackoverflow
Solution 4 - PhpGrahamView Answer on Stackoverflow
Solution 5 - Phpk102View Answer on Stackoverflow
Solution 6 - PhpMicha93View Answer on Stackoverflow
Solution 7 - PhpJonathan MullerView Answer on Stackoverflow
Solution 8 - PhpmarianbodaView Answer on Stackoverflow
Solution 9 - Phpthe manh NguyenView Answer on Stackoverflow
Solution 10 - PhpSharad PawarView Answer on Stackoverflow
Solution 11 - PhpkasimirView Answer on Stackoverflow
Solution 12 - PhpjackView Answer on Stackoverflow
Solution 13 - PhpujovladoView Answer on Stackoverflow
Solution 14 - PhpKenzoView Answer on Stackoverflow
Solution 15 - PhpVladislav SavchukView Answer on Stackoverflow
Solution 16 - PhpHasib Kamal ChowdhuryView Answer on Stackoverflow
Solution 17 - PhpParvez AlamView Answer on Stackoverflow
Solution 18 - PhpAriaanView Answer on Stackoverflow
Solution 19 - PhpPratik SoniView Answer on Stackoverflow
Solution 20 - PhpFilipView Answer on Stackoverflow
Solution 21 - PhpMexidenseView Answer on Stackoverflow
Solution 22 - Phpwebcoder.co.ukView Answer on Stackoverflow
Solution 23 - PhpAmit GargView Answer on Stackoverflow
Solution 24 - PhpDIDIT BView Answer on Stackoverflow
Solution 25 - PhpAhmad SharifView Answer on Stackoverflow