PHP正则表达式检查日期是否为YYYY-MM-DD格式

130

我想检查最终用户输入的日期格式是否为YYYY-MM-DD。正则表达式从来不是我的强项,我设置的preg_match()一直返回false。

所以我认为我在正则表达式上搞砸了,详见下文。

$date="2012-09-12";

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

有什么想法吗?


6
正则表达式不足以验证日期。在使用正则表达式后,你还应该使用以下两种方式之一:date("Y-m-d", strtotime("2012-09-12"))=="2012-09-12"; 或者 PHP 的 checkdate ( int $month , int $day , int $year ) - oxygen
1
我现在并不想验证它,我只是想确保它的格式为YYYY-MM-DD。 - cosmicsafari
2
对于用户输入的值,除了在表单提交后立即进行正则表达式检验(以便您可以显示错误消息)之外,还有什么更好的“时机”来验证呢? - oxygen
公正的观点,可以避免以后的小问题。 - cosmicsafari
27个回答

230

试试这个。

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

22
可以翻译为:return (bool)preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/",$date);,意思是验证$date是否符合YYYY-MM-DD日期格式的正则表达式,并返回布尔值。 - oxygen
7
为什么这个答案被标记为正确答案?即使对于类似2016-02-30和2016-09-31这样的无效日期,它也会返回true。 - Graham
10
这个问题是关于如何检查日期格式(YYYY-MM-DD),被标记为已接受的答案并不总是最佳答案,它只是意味着对提问者有效。阅读这个漂亮的导览:http://stackoverflow.com/tour。 - stramin
2
@Graham 老帖子了,但我同意 Stramin 的观点 - 这个问题是关于格式验证的,而不是实际日期验证。 - treyBake
应该很简单(我的一个答案在页面上正确格式化):$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 '无效的开始日期,例如YYYY-MM-DD'; } - webcoder.co.uk
显示剩余2条评论

136

对于此事,使用另一种机制可能会更好。

使用DateTime的现代解决方案:

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

这也验证了输入:$dt !== false 确保日期可以使用指定的格式解析,而 array_sum 技巧是一种简洁的方式,确保 PHP 没有进行“月份移位”(例如,将1月32日视为2月1日)。有关更多信息,请参阅 DateTime::getLastErrors()

旧式解决方案使用 explodecheckdate

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

这也会验证输入是否为有效日期。当然,您可以使用正则表达式来实现,但这可能会更麻烦--而且根本无法使用正则表达式验证2月29日。

这种方法的缺点是您必须非常小心地拒绝所有可能的“错误”输入,同时在任何情况下都不发出通知。如下:

  • explode 仅返回3个标记(因此如果输入为“1-2-3-4”,$d 将变为“3-4”)
  • ctype_digit 用于确保输入不包含任何非数字字符(除了破折号)
  • array_pad 被使用(使用一个默认值将导致 checkdate 失败),以确保返回足够的元素,因此如果输入为“1-2” ,list() 将不会发出通知

+1,我总是使用DateTime,从未听说过checkdate...真丢人。 - k102
@k102:DateTime 也可以实现这个功能。我刚刚完善了答案,如果您愿意可以再看一遍。 - Jon
1
@user2428118:你是按照解决方案#1的全部步骤尝试了一遍,还是只尝试了第一步?你有点击我提供的getLastErrors文档链接吗? - Jon
DateTime::createFromFormat("Y-m-d", $date); 这将允许传递单个数字的月份。例如,2021-3-23 是有效的,而用户显然只想允许 2021-03-23。 - Ouroboros
1
我建议在PHP 8中使用以下代码:$dt = DateTime::createFromFormat("Y-m-d", $date); return $dt !== false && (false === $dt::getLastErrors() || !array_sum($dt::getLastErrors())); - paaacman
显示剩余3条评论

51
/^(0[1-9]|1[012])\-(0[1-9]|[12]\d|3[01])\-(19|[2-9]\d)\d{2}$/g

mm/dd/yyyy : /^(0[1-9]|1[012])\/(0[1-9]|[12]\d|3[01])\/(19|[2-9]\d)\d{2}$/g

这些代码是用于验证日期格式是否为yyyy-mm-dd,yyyy/mm/dd,mm-dd-yyyy或mm/dd/yyyy。其中yyyy表示4位年份,mm表示2位月份,dd表示2位日期。 此正则表达式使用“|”运算符将四种情况合并在一起,并使用“^”和“$”限制字符串的开头和结尾。在正则表达式中使用“\”转义字符来匹配“-”和“/”字符。/^(((0[1-9]|[12]\d|3[01])\/(0[13578]|1[02])\/((19|[2-9]\d)\d{2}))|((30|[12]\d|0[1-9])\/(0[13456789]|1[012])\/((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

这些是日期格式的正则表达式,其中/g表示全局匹配。第一个正则表达式是指匹配格式为 年-月-日 的日期,第二个和第三个正则表达式分别是指匹配格式为 月/日/年日/月/年 的日期。请注意保留所有HTML标签。

PS:由于该内容并非IT技术内容,因此我需要推荐您联系中文机器翻译API或寻找专业人员进行翻译。

日期格式验证正则表达式: /^(((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

这段内容是关于日期格式验证的正则表达式,其中“\/”和“\-”分别代表斜杠和短横线。该表达式可以用于验证输入的日期字符串是否符合指定格式,日期格式为dd/mm/yyyy或dd-mm-yyyy。

上述所有工作适用于1900年至9999年的日期,这是大多数情况下所需的,并且比我的尝试(关于yyyy-mm-dd格式)短74个字符。如果您需要处理1900年以前的日期,则对Shyju答案进行微小调整即可允许从1000年开始,并且又可以节省23个字符:^((([1-9]\d{3})-(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))|(([1-9]\d{3})-02-(0[1-9]|1\d|2[0-8]))|(([1-9]\d(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-02-29))$ - Graham

40

标准:

每个能被4整除的年份都是闰年,但被100整除的年份除外,除非它能被400整除。因此:

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

闰年二月有29天,非闰年为28天

四月、六月、九月和十一月每月30天

一月、三月、五月、七月、八月、十月和十二月每月31天

测试:

以下日期都应通过验证:

1976-02-29
2000-02-29
2004-02-29
1999-01-31
以下日期都应该无法通过验证:
2015-02-29
2015-04-31
1900-02-29
1999-01-32
2015-02-00

范围:

我们将测试从公元1000年1月1日到2999年12月31日的日期。技术上,目前使用的公历仅在1753年开始用于英国帝国,在17世纪的不同年份用于欧洲国家,但我不会担心这个。

用于测试闰年的正则表达式:

能被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

可以被4整除的年份:

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

可被100整除的年份:

[12]\d00

不能被100整除:

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

能被100整除但不能被400整除的年份:

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

能被4整除但不能被100整除:

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

闰年:

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])

不可以被4整除:

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

非闰年:

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

除了二月份以外的有效月份和日期(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))

二月有28天:

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

二月有29天:

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

有效日期:

(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]))))

这里有一个针对日期在公元1000年1月1日至公元2999年12月31日之间,使用YYYY-MM-DD格式的正则表达式。

我猜它可以被缩短很多,但我会把它留给其他人来完成。

它将匹配所有有效的日期。如果你希望它只在包含一个日期且没有其他内容时才有效,则像这样将其包裹在^( )$中:

^(((((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])))))$
如果您希望它用于可选日期输入(即可以为空或为有效日期),则在开头添加^$|,如下所示:
^$|^(((((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])))))$

9
那是英勇的表现,并且解释得非常好。 - vimfluencer
你有强有力的证据表明它能通过每个有效的案例,并在每个无效的案例上失败吗? - Md Ashraful Islam
@Md Ashraful Islam 不。 - Graham
@Graham,虽然没有问题,但我们正在网站中使用它。希望不会出现问题。 - Md Ashraful Islam
@Md Ashraful Islam - 我们也使用它,并且迄今为止没有遇到任何问题。 - Graham

20
你可以这样做:

你可以这样做:

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

但你最好使用这个:

$date = DateTime::createFromFormat('Y-m-d', $date);
if ($date) {
    echo $date -> format('Y-m-d');
}
在这种情况下,您将获得一个对象,它比仅使用字符串要容易得多。

在这种情况下,您将获得一个对象,它比仅使用字符串要容易得多。


3
此处使用的dateTime技术会将像2016-05-44这样的日期识别为真,但该日期并不存在。 - nonybrighto
这不是一个好的例子,特别是如果你想要获取日期(Y-m-d)。例如:0175-44-19927 将会通过。 - Attila Naghi
我看不到 ^$ - Ismail

11

我知道这是一个老问题。但我认为我有一个好的解决方案。

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

if(date($format, strtotime($date)) == date($date)) {
    echo "true";
} else {
    echo "false";
}
你可以试试。如果你将日期更改为2016年2月21日,那么echo就是false。而且如果你在此之后更改格式为d.m.Y,echo就会是true。
使用这个简单的代码,你应该能够检查使用了哪种日期格式,而无需通过正则表达式进行检查。
也许有人会在每种情况下测试它。但我认为我的想法通常是有效的。对我来说,它似乎是合乎逻辑的。

我认为在if语句中不需要将date()函数用于$date变量。 如果(date($format, strtotime($date)) == $date),这应该就足够了。 - iateadonut
这似乎运行良好,而且它能够正确处理闰年。 - AdamJones

8
您可以使用 preg_match 函数结合 checkdate 函数来实现。
$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;

return preg_match("/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/", $date, $split)) && checkdate($split[2],$split[3],$split[1]); - oxygen

7

验证通用日期格式的函数:

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

执行示例:

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

请在回答中始终提供上下文,而不仅仅是粘贴代码。有关更多详细信息,请参见此处。请还编辑您的答案以包含 markdown 块中的所有代码部分。 - gehbiszumeis
以下是有关编程的内容,从英语翻译成中文。仅返回翻译后的文本:来源:https://www.php.net/manual/en/function.checkdate.php#113205 - paaacman

7

对某些人可能有用:

$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

2
'1996-11-31 24:00'); // true', 真的吗?此外,有时一分钟会有61秒,请参见:https://en.wikipedia.org/wiki/Leap_second - Toto

6

preg_match需要一个/或其他字符作为分隔符。

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

你还应该检查日期的有效性,以免出现像9999-19-38这样的情况。

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

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