242

I'm receiving a date string from an API, and it is formatted as yyyy-mm-dd.

I am currently using a regex to validate the string format, which works ok, but I can see some cases where it could be a correct format according to the string but actually an invalid date. i.e. 2013-13-01, for example.

Is there a better way in PHP to take a string such as 2013-13-01 and tell if it is a valid date or not for the format yyyy-mm-dd?

Amal Murali
  • 75,622
  • 18
  • 128
  • 150
Marty Wallace
  • 34,046
  • 53
  • 137
  • 200

20 Answers20

580

You can use DateTime::createFromFormat() for this purpose:

function validateDate($date, $format = 'Y-m-d')
{
    $d = DateTime::createFromFormat($format, $date);
    // The Y ( 4 digits year ) returns TRUE for any integer with any number of digits so changing the comparison from == to === fixes the issue.
    return $d && $d->format($format) === $date;
}

[Function taken from this answer. Also on php.net. Originally written by Glavić.]


Test cases:

var_dump(validateDate('2013-13-01'));  // false
var_dump(validateDate('20132-13-01')); // false
var_dump(validateDate('2013-11-32'));  // false
var_dump(validateDate('2012-2-25'));   // false
var_dump(validateDate('2013-12-01'));  // true
var_dump(validateDate('1970-12-01'));  // true
var_dump(validateDate('2012-02-29'));  // true
var_dump(validateDate('2012', 'Y'));   // true
var_dump(validateDate('12012', 'Y'));  // false

Demo!

ᴍᴇʜᴏᴠ
  • 4,804
  • 4
  • 44
  • 57
Amal Murali
  • 75,622
  • 18
  • 128
  • 150
  • 17
    If you are using PHP 5.2.x, you should use `strtotime` to get the unix timestamp then `date('Y-m-d', $t)` to get the string date. Then you compare them just like this answer. – pedromanoel Jun 24 '14 at 14:00
  • 4
    @pedromanoel: for standard datetime input you can use `strtotime`, but for non-standard formats that `strtotime` doesn't recognizes, you will need some other solution. And for php version 5.2 support stopped on January 2011, for 5.3 support stopped on August 2014. – Glavić Oct 23 '14 at 15:56
  • 7
    consider this date `var_dump( validateDate('2012-2-9'));` – reignsly Jun 10 '15 at 05:59
  • 7
    The function works correctly. It returned false because thr format you specified was incorrect. If you want to use day and month without leading zeroes, then the format should be `'Y-n-j'`, @reignsly. – Amal Murali Jun 10 '15 at 10:04
  • I don't see the point of ` $d->format('Y-m-d') == $date` part since $d would already be false if it wasn't the correct format? – Antony D'Andrea Jul 01 '15 at 15:35
  • 1
    @AntonyD'Andrea: no, it will not work without that part. Because `$d` will not be false in you give it date, which has overflown parts, like 13th month (2013-13-01). But it really depends what you want. If you need for example `validateDate('2015-55-66')` to be valid, then yes, you only need to check if `$d` is object or not. – Glavić Jul 02 '15 at 05:42
  • why this is false var_dump( validateDate('2013-13-01') ); this also a valid Y-d-m format date – arsh Aug 21 '16 at 05:48
  • @arsh: Yes, it's a valid `Y-d-m` format date. But in the function, I've given `Y-m-d`. – Amal Murali Aug 21 '16 at 06:04
  • Sorry, I just wandering for a function to determine any string is it date or not – arsh Aug 21 '16 at 06:12
  • this will return false on a date like this `'2016-12-11 10:15:00'` – albanx Oct 25 '16 at 10:01
  • @albanx: You can supply the date format as well. I have hardcoded it in the function, but you can change that however you want. `Y-m-d H:i:s`, for example. – Amal Murali Jan 07 '17 at 21:58
  • You, also should check about DateTime::getLastErrors() – danielpopa Jul 17 '17 at 10:09
  • should be return $dt && (strtolower($dt->format($format)) == strtolower($string)); to make it work with 'january 2017' like formats – orbitory Dec 08 '17 at 22:25
  • 1
    You should prepend '!' to the date format string to reset any non-defined date-time properties to 00 (unix epoch time). Otherwise with a pattern like 'Y-m' createFromFormat() will use the current time, which can lead to issues when converting timezones. – Tall Sam May 30 '18 at 02:05
  • @Glavić Do you think the strict comparison is required here? – Amal Murali Jul 17 '18 at 01:32
  • @AmalMurali: I have not used strict comparison in my answers; I even used string and int in my examples as valid (output true). It is all based on user requirements, but I think most users will not need strict comparisons. – Glavić Jul 17 '18 at 11:05
  • Nonsense, why 12012 would be a bad year? WTH? Do they expect Earth or callendars are not gonna exist in the year of 12012? – TomeeNS Feb 13 '19 at 16:44
  • does not work for var_dump(validateDate('0018-08-23')); – vlr Oct 01 '19 at 21:35
  • `var_dump(validateDate('9:00'));` // false – 风声猎猎 Aug 01 '20 at 07:21
99

Determine if any string is a date

function checkIsAValidDate($myDateString){
    return (bool)strtotime($myDateString);
}
mustafa.0x
  • 1,476
  • 2
  • 17
  • 30
arsh
  • 1,218
  • 8
  • 5
  • 3
    This validates a whole range of valid date formats not just `yyyy-mm-dd`. – EWit Jun 25 '14 at 06:36
  • 12
    @MichelAyres this is because php sees `2015-02-30` as a valid date because when the day given is greater than the number of days in the given month (or negative) php rolls over to the next month. Since the date is guaranteed to be of the format `yyyy-mm-dd` this can be fixed by changing the return to `return (bool)strtotime($myDateString) && date("Y-m-d", strtotime($myDateString)) == $myDateString;`. – elitechief21 Feb 25 '15 at 16:45
  • 3
    Why does `(bool)strtotime('s')` come out as true? – Peon Mar 31 '15 at 07:29
  • 1
    this also returns 1 checkIsAValidDate("F"); – Vaibhav Bhanushali Jan 20 '16 at 16:51
  • we can use `$myDateString = str_replace("/", '-', $myDateString);` before return if the date string contains slashes (/) like :- dd/mm/yyyy – Yashrajsinh Jadeja Aug 02 '16 at 05:22
  • @vaibhav,@Dainis echo "$str == " . date('l dS \o\f F Y h:i:s A', $timestamp); – Pasha.A Nov 15 '16 at 23:54
  • Important to note that this won't work with a British (d/m/Y) format as it will assume it's converting the American (m/d/Y). It will seem to work only if the day is lower than 12. – Sylvester Saracevas May 01 '18 at 13:00
  • 1
    This is not correct! Will validate if you give it any integer or decimal! Use this only if you are sure that a string is being fed into the function. – Oliver M Grech Dec 03 '19 at 11:07
  • this is incorrect (bool)strtotime("foo") returns true – TV-C-1-5 Mar 03 '23 at 18:17
  • This accepted answer is an error - why is the error answer accepted - and this not the answer? how do we fix this? We are sending people off with the wrong answer and to potential doom :/ and ... go figure - the person who posted this is aware it is an error and allows himself to collect points for an incorrect answer... figures. – TV-C-1-5 Mar 03 '23 at 18:36
47

Use in simple way with php prebuilt function:

function checkmydate($date) {
  $tempDate = explode('-', $date);
  // checkdate(month, day, year)
  return checkdate($tempDate[1], $tempDate[2], $tempDate[0]);
}

Test

   checkmydate('2015-12-01'); //true
   checkmydate('2015-14-04'); //false
Victor Schröder
  • 6,738
  • 2
  • 42
  • 45
vineet
  • 13,832
  • 10
  • 56
  • 76
  • Again, when using a test, inside an `if` to return simply `true` or `false`, return the test itself. – Victor Schröder Feb 06 '17 at 15:21
  • 4
    This assumes there are at least 3 elements in the $tempDate array. – person27 Mar 08 '17 at 01:30
  • 3
    @person27: `return sizeof($tmpDate) == 3 && checkdate($tmpDate[1]...` – neurino Jun 15 '18 at 13:22
  • @vineet - this fails. If the year is `2`, `20`, `202`, `2020` or even if the year is `20201` - it returns true every time. – rolinger May 07 '20 at 20:47
  • d.m.Y is standart for almost every european country - and there are cases where it's something like Y/m/d [ although i don't understand how you could print day after month lol ] - you don't want to explode every possible input string. – clockw0rk Aug 21 '20 at 11:31
  • This throws notices if `checkmydate('thisIsWrong!')` – nulll Mar 07 '22 at 10:51
19

Determine if string is a date, even if string is a non-standard format

(strtotime doesn't accept any custom format)

<?php
function validateDateTime($dateStr, $format)
{
    date_default_timezone_set('UTC');
    $date = DateTime::createFromFormat($format, $dateStr);
    return $date && ($date->format($format) === $dateStr);
}

// These return true
validateDateTime('2001-03-10 17:16:18', 'Y-m-d H:i:s');
validateDateTime('2001-03-10', 'Y-m-d');
validateDateTime('2001', 'Y');
validateDateTime('Mon', 'D');
validateDateTime('March 10, 2001, 5:16 pm', 'F j, Y, g:i a');
validateDateTime('March 10, 2001, 5:16 pm', 'F j, Y, g:i a');
validateDateTime('03.10.01', 'm.d.y');
validateDateTime('10, 3, 2001', 'j, n, Y');
validateDateTime('20010310', 'Ymd');
validateDateTime('05-16-18, 10-03-01', 'h-i-s, j-m-y');
validateDateTime('Monday 8th of August 2005 03:12:46 PM', 'l jS \of F Y h:i:s A');
validateDateTime('Wed, 25 Sep 2013 15:28:57', 'D, d M Y H:i:s');
validateDateTime('17:03:18 is the time', 'H:m:s \i\s \t\h\e \t\i\m\e');
validateDateTime('17:16:18', 'H:i:s');

// These return false
validateDateTime('2001-03-10 17:16:18', 'Y-m-D H:i:s');
validateDateTime('2001', 'm');
validateDateTime('Mon', 'D-m-y');
validateDateTime('Mon', 'D-m-y');
validateDateTime('2001-13-04', 'Y-m-d');
enigma
  • 3,476
  • 2
  • 17
  • 30
migli
  • 2,692
  • 27
  • 32
17

This option is not only simple but also accepts almost any format, although with non-standard formats it can be buggy.

$timestamp = strtotime($date);
return $timestamp ? $date : null;
galki
  • 8,149
  • 7
  • 50
  • 62
  • This should have been the accepted answer! Much, much simpler. – Webmaster G Feb 26 '16 at 17:44
  • 2
    Important to note that this won't work with a British (d/m/Y) format as it will assume it's converting the American (m/d/Y). It will seem to work only if the day is lower than 12! – Sylvester Saracevas May 01 '18 at 12:58
  • @galki - i tested this and it fails certain values. If $date = '202-03-31' it returns true. But thats not a valid date. I found that if you alter the year to a non-valid year it always returns true. – rolinger May 07 '20 at 18:23
  • @rolinger strange... maybe it’s seeing 202AD? What year’s timestamp does ‘strtotime’ give? – galki May 07 '20 at 18:28
  • @galki - not certain, but `202` returns a negative number - which still passes the test. – rolinger May 07 '20 at 20:41
  • @rolinger I can see a case where someone born in say 1950 would want their birthday to pass as a valid date, and that value would also probably be negative – galki May 07 '20 at 21:27
  • the cleanest and simplest answer is by user4600953 below: 'function isValidDate($date) { return date('Y-m-d', strtotime($date)) === $date; }' – rolinger May 07 '20 at 21:34
  • 3
    @rolinger it's slower and more code for the same result. '202-03-31' was a real date in the 3rd century, I don't understand why you claim it's not a valid date. – galki May 07 '20 at 23:07
  • why is the error answer accepted - and this not the answer? how do we fix this? We are sending people off with the wrong answer and to potential doom :/ – TV-C-1-5 Mar 03 '23 at 18:35
13

The easiest way to check if given date is valid probably converting it to unixtime using strtotime, formatting it to the given date's format, then comparing it:

function isValidDate($date) {
    return date('Y-m-d', strtotime($date)) === $date;
}

Of course you can use regular expression to check for validness, but it will be limited to given format, every time you will have to edit it to satisfy another formats, and also it will be more than required. Built-in functions is the best way (in most cases) to achieve jobs.

linktoahref
  • 7,812
  • 3
  • 29
  • 51
  • 1
    I feel that this answer is pretty low quality, especially considering there are _already_ strtotime answers. – GrumpyCrouton Jul 03 '17 at 19:51
  • 5
    This is the shortest answer and it works. It is a bit harsh to say that it is low quality. – Tim Rogers Aug 16 '18 at 09:30
  • @user4600953 - this is the easiest one that works. Lots of others are saying to use `checkdate()` - but I am finding checkdate fails if the year is ANY value: `2, 20, 202, 2020, 20201` - all return true. I am going with your solution! – rolinger May 07 '20 at 21:30
12

You can also Parse the date for month date and year and then you can use the PHP function checkdate() which you can read about here: http://php.net/manual/en/function.checkdate.php

You can also try this one:

$date="2013-13-01";

if (preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/",$date))
    {
        echo 'Date is valid';
    }else{
        echo 'Date is invalid';
    }
Suvash sarker
  • 3,140
  • 1
  • 18
  • 21
  • in case of february (as commented by elitechief21) function isValidDate($date) { return preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/",$date) && date("Y-m-d", strtotime($date)) == $date; } – abdulwadood Aug 12 '16 at 14:03
  • 4
    This solution is very poor, as it doesn't check the date validity in any sense. Any month can have 31 days, including February. Any year can have 29 of February. To validate dates using RegExp would demand something much more complex, with back-references and negative look aheads. – Victor Schröder Feb 06 '17 at 15:34
12

I'm afraid that most voted solution (https://stackoverflow.com/a/19271434/3283279) is not working properly. The fourth test case (var_dump(validateDate('2012-2-25')); // false) is wrong. The date is correct, because it corresponds to the format - the m allows a month with or without leading zero (see: http://php.net/manual/en/datetime.createfromformat.php). Therefore a date 2012-2-25 is in format Y-m-d and the test case must be true not false.

I believe that better solution is to test possible error as follows:

function validateDate($date, $format = 'Y-m-d') {
    DateTime::createFromFormat($format, $date);
    $errors = DateTime::getLastErrors();

    return $errors['warning_count'] === 0 && $errors['error_count'] === 0;
}
Barvajz
  • 210
  • 2
  • 6
7

Accordling with cl-sah's answer, but this sound better, shorter...

function checkmydate($date) {
  $tempDate = explode('-', $date);
  return checkdate($tempDate[1], $tempDate[2], $tempDate[0]);
}

Test

checkmydate('2015-12-01');//true
checkmydate('2015-14-04');//false
sdotbertoli
  • 131
  • 1
  • 4
7

I have this thing that, even with PHP, I like to find functional solutions. So, for example, the answer given by @migli is really a good one, highly flexible and elegant.

But it has a problem: what if you need to validate a lot of DateTime strings with the same format? You would have to repeat the format all over the place, what goes against the DRY principle. We could put the format in a constant, but still, we would have to pass the constant as an argument to every function call.

But fear no more! We can use currying to our rescue! PHP doesn't make this task pleasant, but it's still possible to implement currying with PHP:

<?php
function validateDateTime($format)
{
    return function($dateStr) use ($format) {
        $date = DateTime::createFromFormat($format, $dateStr);
        return $date && $date->format($format) === $dateStr;
    };
}

So, what we just did? Basically we wrapped the function body in an anonymous and returned such function instead. We can call the validation function like this:

validateDateTime('Y-m-d H:i:s')('2017-02-06 17:07:11'); // true

Yeah, not a big difference... but the real power comes from the partially applied function, made possible by currying:

// Get a partially applied function
$validate = validateDateTime('Y-m-d H:i:s');

// Now you can use it everywhere, without repeating the format!
$validate('2017-02-06 17:09:31'); // true
$validate('1999-03-31 07:07:07'); // true
$validate('13-2-4 3:2:45'); // false

Functional programming FTW!

Victor Schröder
  • 6,738
  • 2
  • 42
  • 45
  • 2
    The best answer by a landslide IMHO(solves the specific problem of the OP with greater flexibility and pretty much the same amount of code as the rest of the answers) – StubbornShowaGuy Feb 08 '17 at 05:59
  • IMHO this is really ugly programming, just put your values in an array and loop through them to validate, but please don't do this! – Tim Jan 02 '18 at 10:58
  • I'm curious @Tim, could you give us an example of your array/loop validation? – Victor Schröder Jan 02 '18 at 14:16
4

How about this one?

We simply use a try-catch block.

$dateTime = 'an invalid datetime';

try {
    $dateTimeObject = new DateTime($dateTime);
} catch (Exception $exc) {
    echo 'Do something with an invalid DateTime';
}

This approach is not limited to only one date/time format, and you don't need to define any function.

Julian
  • 4,396
  • 5
  • 39
  • 51
  • this will not work for datetime value 0000-00-00 00:00:00 – Naseeruddin V N Dec 05 '17 at 09:40
  • @NaseeruddinVN `'0000-00-00 00:00:00'` is a valid datetime value. It's just the first the value. However, the date property of the datetime object will be `'-0001-11-30 00:00:00'`. – Julian Dec 07 '17 at 09:35
4

Validate with checkdate function:

$date = '2019-02-30';

$date_parts = explode( '-', $date );

if(checkdate( $date_parts[1], $date_parts[2], $date_parts[0] )){
    //date is valid
}else{
    //date is invalid
}
Prince Ahmed
  • 1,038
  • 10
  • 10
1

Tested Regex solution:

    function isValidDate($date)
    {
            if (preg_match("/^(((((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])))))$/", $date)) {
                    return $date;
            }
            return null;
    }

This will return null if the date is invalid or is not yyyy-mm-dd format, otherwise it will return the date.

Akumaburn
  • 530
  • 1
  • 5
  • 17
1
/*********************************************************************************
Returns TRUE if the input parameter is a valid date string in "YYYY-MM-DD" format (aka "MySQL date format")
The date separator can be only the '-' character.
*********************************************************************************/
function isMysqlDate($yyyymmdd)
{
    return checkdate(substr($yyyymmdd, 5, 2), substr($yyyymmdd, 8), substr($yyyymmdd, 0, 4)) 
        && (substr($yyyymmdd, 4, 1) === '-') 
        && (substr($yyyymmdd, 7, 1) === '-');
}
Marco Demaio
  • 33,578
  • 33
  • 128
  • 159
1

To add onto the accepted answer, you can further check for a valid date or DateTime by checking if the formatted date is an instanceof DateTime.

$date = DateTime::createFromFormat('Ymd', $value);
$is_datetime = ($date instanceof DateTime);
$is_valid_datetime_format = $is_datetime
  ? ($date->format('Ymd') === $value)
  : false;

if (!$is_datetime || !$is_valid_datetime_format) {
  // Not a valid date.
  return false;
}

This will catch any values that are not a DateTime such as random strings or an invalid date such as 20202020.

Jake Stewart
  • 143
  • 2
  • 12
0
    /**** date check is a recursive function. it's need 3 argument 
    MONTH,DAY,YEAR. ******/

    $always_valid_date = $this->date_check($month,$day,$year);

    private function date_check($month,$day,$year){

        /** checkdate() is a php function that check a date is valid 
        or not. if valid date it's return true else false.   **/

        $status = checkdate($month,$day,$year);

        if($status == true){

            $always_valid_date = $year . '-' . $month . '-' . $day;

            return $always_valid_date;

        }else{
            $day = ($day - 1);

            /**recursive call**/

            return $this->date_check($month,$day,$year);
        }

    }
Unamata Sanatarai
  • 6,475
  • 3
  • 29
  • 51
0

Try and let me know it works for me

$date = \DateTime::createFromFormat('d/m/Y', $dataRowValue);
if (!empty($date)) {
//Your logic
}else{
//Error
}

if you pass any alpha or alphanumberic values it will give you the empty value in return

0

Regex solution

function verify_date($date){
  /* correct format = "2012-09-15 11:23:32" or "2012-09-15"*/
  if (preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])( (0[0-9]|[1-2][0-4]):(0[0-9]|[1-5][0-9]):(0[0-9]|[1-5][0-9]))?$/",$date)) {
    return true;
  } else {
    die("Wrong date format: it should be '2012-09-15 11:23:32' or '2012-09-15', date received is: ".$date);
  }
}
seb_dom
  • 113
  • 7
0

Try this:

$myday = '2022-1-30'; 
if (($timestamp = strtotime($myday)) === false) {
    echo 'The string ('.$myday.') is not date';
} else {
    echo 'The string ('.$myday.') is date = ' . date('l dS \o\f F Y h:i:s A', $timestamp);
}
Stieranka
  • 72
  • 3
-1

Give this a try:

$date = "2017-10-01";


function date_checker($input,$devider){
  $output = false;

  $input = explode($devider, $input);
  $year = $input[0];
  $month = $input[1];
  $day = $input[2];

  if (is_numeric($year) && is_numeric($month) && is_numeric($day)) {
    if (strlen($year) == 4 && strlen($month) == 2 && strlen($day) == 2) {
      $output = true;
    }
  }
  return $output;
}

if (date_checker($date, '-')) {
  echo "The function is working";
}else {
  echo "The function isNOT working";
}
CyanCoding
  • 1,012
  • 1
  • 12
  • 35