正确判断日期字符串是否符合该格式的有效日期。

245

我从API接收到一个日期字符串,它的格式为yyyy-mm-dd

我目前使用正则表达式验证字符串格式,这个方法还不错,但是我发现有一些情况下,即使按照字符串的格式来看也可能是无效的日期,比如2013-13-01

在PHP中,有没有更好的方法来判断像2013-13-01这样的字符串是否是符合yyyy-mm-dd格式的有效日期?


可能是php日期验证的重复问题。 - Vivek Athalye
20个回答

583
你可以使用DateTime::createFromFormat()来实现这个目的:
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;
}

[该函数取自此答案。也在php.net上。最初由Glavić编写。]


测试用例:

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

演示!


19
如果您正在使用 PHP 5.2.x 版本,您应该使用 strtotime 函数获取 Unix 时间戳,然后使用 date('Y-m-d', $t) 函数获取日期字符串。然后,您可以像这个答案一样进行比较。 - pedromanoel
4
对于标准日期时间格式,您可以使用strtotime函数进行转换。但是对于strtotime无法识别的非标准格式,您需要另寻他法。另外,PHP版本5.2支持已于2011年1月停止,5.3支持已于2014年8月停止。 - Glavić
7
请考虑此日期 var_dump( validateDate('2012-2-9')); - reignsly
7
函数运行正常。它返回 false 是因为你指定的格式不正确。如果你想要使用没有前导零的日和月份,则格式应该是 'Y-n-j',@reignsly。 - Amal Murali
1
@AntonyD'Andrea:不,没有那部分代码是行不通的。因为如果你给出了一个日期,其中包含了溢出的部分,比如13个月(2013-13-01),那么$d将不会是false。但这真的取决于你想要什么。如果你需要例如validateDate('2015-55-66')是有效的,那么是的,你只需要检查$d是否为对象即可。 - Glavić
显示剩余14条评论

99

确定任何字符串是否为日期

function checkIsAValidDate($myDateString){
    return (bool)strtotime($myDateString);
}

3
这会验证整个有效日期格式范围,而不仅仅是 yyyy-mm-dd - EWit
12
这是因为PHP将"2015-02-30"视为有效日期,因为当给定的日大于给定月份的天数(或者为负数)时,PHP会滚动到下一个月。由于日期格式保证为"yyyy-mm-dd",因此可以通过更改返回值为return (bool)strtotime($myDateString) && date("Y-m-d", strtotime($myDateString)) == $myDateString;来解决该问题。 - elitechief21
4
为什么(bool)strtotime('s')的结果是true? - Peon
1
checkIsAValidDate("F"); 返回1 - Vaibhav Bhanushali
1
这不正确!如果输入任何整数或小数,将会验证!仅在确定字符串被馈入函数时使用。 - Oliver M Grech
显示剩余5条评论

48

使用PHP预置函数,以简单的方式进行操作:

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

测试

   checkmydate('2015-12-01'); //true
   checkmydate('2015-14-04'); //false

当在使用测试时,在 if 语句中返回 truefalse,应该直接返回测试本身。 - Victor Schröder
4
假设$tempDate数组中至少有3个元素。 - person27
3
@person27: 返回 sizeof($tmpDate) == 3 && checkdate($tmpDate[1]... - neurino
@vineet - 这个失败了。如果年份是2202022020,甚至是20201,每次都会返回true。 - rolinger
d.m.Y 在几乎所有欧洲国家都是标准格式,但有些情况下会使用 Y/m/d 的格式 [尽管我不明白为什么要先输出月份再输出日期 lol] - 您不希望处理每一个可能的输入字符串。 - clockw0rk
如果 checkmydate('thisIsWrong!'),则会抛出通知。 - nulll

19

确定字符串是否为日期,即使字符串是非标准格式

(strtotime不接受任何自定义格式)

<?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');

当在if语句中使用测试来返回truefalse时,应该返回测试本身。 - Victor Schröder
1
但是2018-3-24返回false,该方法接收2018-3-24,当应用格式时返回2018-03-24;我如何以两种方式返回true? - Aquiles Perez

17

这个选项不仅简单易用,而且几乎可以接受任何格式,尽管对于非标准格式可能会有一些问题。

$timestamp = strtotime($date);
return $timestamp ? $date : null;

这应该是被接受的答案!简单多了。 - Webmaster G
2
需要注意的是,这个程序无法使用英国格式(d/m/Y)工作,因为它会误认为正在转换美国格式(m/d/Y)。只有当日期低于12时,才会看起来正常工作! - Sylvester Saracevas
1
@rolinger 奇怪...也许它看到了202AD?strtotime给出的时间戳是哪一年? - galki
最干净和最简单的答案是由用户4600953提供的下面的代码: 'function isValidDate($date) { return date('Y-m-d', strtotime($date)) === $date; }' - rolinger
3
@rolinger 为了达到相同的结果,这种方法更慢且需要更多的代码。 "202-03-31"是公元3世纪的一个真实日期,我不明白你为什么认为它不是有效的日期。 - galki
显示剩余4条评论

13

检查给定日期是否有效的最简单方法可能是使用strtotime将其转换为Unix时间,将其格式化为给定日期的格式,然后进行比较:

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

当然,您可以使用正则表达式来检查有效性,但它将仅限于给定格式,每次您都必须编辑它以满足其他格式,并且这将超出所需。内置函数是实现任务的最佳方式(在大多数情况下)。


1
我觉得这个答案质量相当低,特别是考虑到已经有strtotime的答案了。 - GrumpyCrouton
5
这是最简短的答案,而且它有效。说它质量低有点刻薄。 - Tim Rogers
@user4600953 - 这是最简单有效的方法。许多其他人建议使用checkdate()函数,但我发现如果年份为任何值,checkdate()都会失败:2、20、202、2020、20201 - 都会返回true。我选择采用您的解决方案! - rolinger

12

很遗憾,最受欢迎的解决方案(https://dev59.com/s2Ik5IYBdhLWcg3wbtyb#19271434)并没有正常工作。第四个测试用例 (var_dump(validateDate('2012-2-25')); // false) 是错的。日期是正确的,因为它符合格式 - m 允许一个带或不带前导零的月份(请参阅:http://php.net/manual/en/datetime.createfromformat.php)。因此,日期2012-2-25 是以Y-m-d 格式呈现的,测试用例必须是真的而不是假的。

我认为更好的解决方法是按照以下方式测试可能的错误:

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

    return $errors['warning_count'] === 0 && $errors['error_count'] === 0;
}

12

您也可以解析日期并获取月、日和年,然后使用PHP函数checkdate()进行验证。您可以在此处阅读有关该函数的信息:http://php.net/manual/zh/function.checkdate.php

您也可以尝试以下方法:

$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';
    }

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
4
这个解决方案非常不好,因为它在任何情况下都没有检查日期的有效性。任何一个月都可能有31天,包括二月份。任何一年都可能有29号的二月份。要使用正则表达式验证日期需要更复杂的操作,包括后向引用和负向先行断言。 - Victor Schröder

8

我有这样一种想法,即使是在PHP中,我也喜欢找到功能性解决方案。例如,@migli提供的答案非常好,高度灵活和优雅。

但它有一个问题:如果您需要验证许多具有相同格式的DateTime字符串怎么办?您将不得不在所有地方重复使用格式,这与DRY原则相违背。我们可以将格式放在常量中,但仍然需要将常量作为参数传递给每个函数调用。

但不要担心!我们可以使用柯里化来解决问题!PHP并不会让这项任务变得愉快,但仍然可以在PHP中实现柯里化:

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

那么,我们刚才做了什么?基本上,我们将函数体包装在一个匿名函数中,并返回这样的函数。我们可以像这样调用验证函数:

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

是的,这并没有太大差别…但真正的威力来自于部分应用函数,这得益于柯里化:

// 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

函数式编程太棒了!


2
在我看来,这是最佳答案(解决了OP的特定问题,具有更大的灵活性,并且几乎与其他答案一样少的代码量)。 - StubbornShowaGuy
在我看来,这是非常丑陋的编程方式,只需将您的值放入数组中并循环遍历以进行验证,但请不要这样做! - Tim
我很好奇,@Tim,你能给我们举一个关于数组/循环验证的例子吗? - Victor Schröder

7
根据cl-sah的回答,但这个更好,更短...
function checkmydate($date) {
  $tempDate = explode('-', $date);
  return checkdate($tempDate[1], $tempDate[2], $tempDate[0]);
}

测试

checkmydate('2015-12-01');//true
checkmydate('2015-14-04');//false

你需要测试 count($tempDate) === 3 - VDarricau
@VDarricau 不需要,如果它们缺失,checkdate函数会出错。你可以为每个变量都写一个isset()函数,但我建议直接忽略警告信息。 - Mr Heelis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接