验证 ISO 8601 日期字符串

23
如何验证ISO 8601日期字符串(例如:2011-10-02T23:25:42Z)?
我知道ISO 8601日期有几种可能的表示方式,但我只对上面给出的格式进行验证感兴趣。

1
那看起来不正确。"day" 部分不应该是两位数字吗? - Phil
@Phil - 我想你是对的。我已经更新了帖子以反映这一点。 - titel
6个回答

18
这对我很有帮助,它使用正则表达式确保日期的格式符合要求,然后尝试解析和重新创建日期以确保输出与输入匹配:
<?php

$date = '2011-10-02T23:25:42Z';
var_dump(validateDate($date));

$date = '2011-17-17T23:25:42Z';
var_dump(validateDate($date));

function validateDate($date)
{
    if (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/', $date, $parts) == true) {
        $time = gmmktime($parts[4], $parts[5], $parts[6], $parts[2], $parts[3], $parts[1]);

        $input_time = strtotime($date);
        if ($input_time === false) return false;

        return $input_time == $time;
    } else {
        return false;
    }
}

你可以进一步使用checkdate来确保月份、日期和年份也是有效的。

3
a) 不要使用大小写不敏感标志;TZ是唯一适用的字母字符,应大写。 b) Z不是唯一有效的时区指示符。请参见http://en.wikipedia.org/wiki/ISO_8601#Dates。 - Phil
2
你还应该允许在日期字符串前面加上可选的 +-(http://en.wikipedia.org/wiki/ISO_8601#Years)。可能只需要使用 ([+|-]?) 即可。 - Luciano Mammino

18

我想分享我的轻量级解决方案。它不是理想的,但可能对某些人有帮助。

function validISO8601Date($value)
{
    if (!is_string($value)) {
        return false;
    }

    $dateTime = \DateTime::createFromFormat(\DateTime::ISO8601, $value);

    if ($dateTime) {
        return $dateTime->format(\DateTime::ISO8601) === $value;
    }

    return false;
}

注意!

一些有效的 ISO8601 日期将会失败,请查看下面的列表。

NOT VALID  --> '' // Correct
NOT VALID  --> 'string' // Correct
VALID      --> '2000-01-01T01:00:00+1200' // This is the only format function returns as valid
NOT VALID  --> '2015-01 first' // Correct
NOT VALID  --> '2000-01-01T01:00:00Z' // Must be valid!
NOT VALID  --> '2000-01-01T01:00:00+01' // Must be valid!

这似乎是最简洁的方法,但时区偏移可以包含冒号或不包含在标准中,但日期函数使用它取决于是否使用DATE_ISO8601或“c”来指定格式。 - Grayside
1
@MrBoolean,没错。然后将会把“1970-01-01T01:00:00+0100”与$value进行比较,返回false - Jekis
没错。但是 strtotime 函数也会解析像 a day ago2015-01 first 这样的东西以及其他许多内容。在这一点上,该函数会失败。 - MrBoolean
我已修复了该函数,现在它适用于所有类型的正确操作。 - Jekis
1
来自Zend的人们:这种格式(DateTime :: ISO8601)与ISO-8601不兼容,但出于向后兼容性的原因保留了这种方式。请改用DateTime :: ATOM或DATE_ATOM以代替ISO-8601进行兼容性。 - Duco
显示剩余2条评论

14

编辑:目前最简单的方法是尝试使用字符串创建DateTime对象,例如:

$dt = new DateTime($dateTimeString);

如果DateTime构造函数无法解析字符串,它将抛出异常,例如:

DateTime::__construct():在第18个位置(2)处未预期的字符,未能解析时间字符串(2011-10-02T23:25:72Z)

请注意,如果省略时区标识符,则会使用配置的默认时区。

第二种最简单的方法是使用正则表达式。像这样的内容应该可以覆盖它:

if (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|(\+|-)\d{2}(:?\d{2})?)$/', $dateString, $parts)) {
    // valid string format, can now check parts

    $year  = $parts[1];
    $month = $parts[2];
    $day   = $parts[3];

    // etc
}

11
好的,DateTime构造函数不仅限于ISO 8601输入,这是一个不错的想法。 - Grayside
@Grayside 我不太确定你的意思。我没有说过它有限制。 - Phil
17
如果您想检查日期是否为有效的ISO格式,那么允许其他非ISO格式的日期格式将使其不再验证ISO格式。 - ppetermann
这不足以验证ISO8601标准。例如: new DateTime("22\t12.78"); 将返回一个真值。将接受任何一个格式。 - Duco

6
请参考http://www.pelagodesign.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/,其中提供了一个正则表达式用于验证ISO 8601格式的日期。
^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$

我想这并不能完全回答你的问题,因为它将匹配任何有效的ISO 8601日期,但如果可以接受的话,这个方法是完美的。


此正则表达式甚至将像3401这样的字符串也视为有效日期。 请自行决定是否使用。 - Madhur Bhaiya

5

这是我使用的函数。它类似于drew010的答案,但也适用于以“+01:00”或“-01:00”结尾的时间戳。

function isTimestampIsoValid($timestamp)
{
    if (preg_match('/^'.
            '(\d{4})-(\d{2})-(\d{2})T'. // YYYY-MM-DDT ex: 2014-01-01T
            '(\d{2}):(\d{2}):(\d{2})'.  // HH-MM-SS  ex: 17:00:00
            '(Z|((-|\+)\d{2}:\d{2}))'.  // Z or +01:00 or -01:00
            '$/', $timestamp, $parts) == true)
    {
        try {
            new \DateTime($timestamp);
            return true;
        }
        catch ( \Exception $e)
        {
            return false;
        }
    } else {
        return false;
    }
}

-1

Carbon 在处理这部分非常出色

use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
...
/** @test */
public function checking_iso8601_date()
{
    $this->expectException(InvalidFormatException::class);
    Carbon::createFromFormat('c', '2021-05-11 20:03:45+02:00');

    $this->assertInstanceOf(Carbon::class, Carbon::createFromFormat('c', '2021-05-11T20:03:45+02:00'));
}

你可以用 DateTime 做同样的事情

use DateTime;
...
/** @test */
public function checking_iso8601_date()
{
    $this->assertInstanceOf(DateTime::class, DateTime::createFromFormat('Y-m-d\TH:i:sP', '2021-05-11T20:03:45+02:00'));
    $this->assertFalse(DateTime::createFromFormat('Y-m-d\TH:i:sP', '2021-05-11 20:03:45+02:00'));
}

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