正则表达式匹配ISO 8601日期时间字符串

94

有没有一个好的正则表达式模式可以匹配ISO日期时间?

例如:2010-06-15T00:00:00


2
我使用 /^(\d{4})-0?(\d+)-0?(\d+)[T ]0?(\d+):0?(\d+):0?(\d+)$/,(然而这并不是最严格的一个)... 将其转换为日期对象则是另外一回事 :) - mykhal
12个回答

155

根据 W3C规范 的严格要求,包括毫秒的完整日期时间为:

//-- Complete precision:
/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/

//-- No milliseconds:
/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)/

//-- No Seconds:
/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)/

//-- Putting it all together:
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/

.
根据实际的ISO 8601:2004(E)文件,还可以使用其他变体:

/********************************************
**    No time-zone varients:
*/
//-- Complete precision:
/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+/

//-- No milliseconds:
/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d/

//-- No Seconds:
/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d/

//-- Putting it all together:
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+)|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d)|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d)/

警告:这一切很快就会变得混乱不堪,并且仍然允许某些无意义的情况,比如第14个月。 此外,ISO 8601:2004(E)还允许其他几种变体。

.
"2010-06-15T00:00:00" 不合法,因为它没有时区指定。


2
ISO-8601规定,如果省略时区,则默认为UTC,包括“Z”。 - Scott S. McCoy
1
我完全同意维基百科不是权威来源的说法。但对于某些主题,它可以作为一个合理的参考,而ISO-8601的文章提供了易懂的例子和解释。 :-D - Scott S. McCoy
6
哇,这个答案被引用在Angular源代码中了!@BrockAdams - sidonaldson
3
感谢提供正则表达式。为了微调一下,我们可以使用 (?: ) 而不是 ( ) 来避免捕获一个组。例如第一个正则表达式可以写成 \d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z) - Thanish
5
这将无法处理闰秒,即偶尔出现的第61秒。 - mcfedr
显示剩余9条评论

21

如果只需要匹配 ISO 格式的日期,如 2017-09-22,可以使用以下正则表达式:

\d{4}-\d{2}-\d{2}

^\d{4}-([0]\d|1[0-2])-([0-2]\d|3[01])$
它将匹配任何数字年份,任何在00-12范围内指定两位数字的月份和任何在00-31范围内指定两位数字的日期。

7
00在月份或日期中如何有效? - Yigit Erol
1
它和 2023-02-30 一样无效。最终,正则表达式不适合用于验证日期是否正确,应仅用于指示预期格式。鉴于此,一个更易于阅读但允许一些明显错误的较短格式是一个很好的折衷方案。我实际上更喜欢 ^\d{4}-\d{2}-\d{2}$,因为它更加简洁明了。 - Caoilte

12

我将顶部答案进行了修改,使其更加简洁。不是写出每个三个可选的模式,而是将元素嵌套为可选语句。

/[+-]?\d{4}(-[01]\d(-[0-3]\d(T[0-2]\d:[0-5]\d:?([0-5]\d(\.\d+)?)?[+-][0-2]\d:[0-5]\dZ?)?)?)?/

我想知道这种方法是否有缺点?

你可以在这里找到我的建议的测试结果: http://regexr.com/3e0lh


1
这个无法工作: 2016-09-05T15:22:26.286Z将[+-][0-2]\d:[0-5]\d改为可选项就可以工作了: [+-]?\d{4}(-[01]\d(-[0-3]\d(T[0-2]\d:[0-5]\d:?([0-5]\d(\.\d+)?)?([+-][0-2]\d:[0-5]\d)?Z?)?)?)? - Frank
尝试在Chrome控制台中输入以下内容进行检查:/[+-]?\d{4}(-[01]\d(-[0-3]\d(T[0-2]\d:[0-5]\d:?([0-5]\d(.\d+)?)?[+-][0-2]\d:[0-5]\dZ?)?)?)?/.test("28T09:5220.5690463826472487295963210360122513980205942+01:53");这允许奇怪的东西作为有效值。 - ferpel
这导致我的身份验证令牌被捕获,因为它有一个与部分\d{4}匹配的匹配部分。 - Raddish IoW

12
我已经制作了这个正则表达式,解决了从Javascript的.toISOString()方法中提取日期的验证问题。

^[0-9]{4}-((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01])|(0[469]|11)-(0[1-9]|[12][0-9]|30)|(02)-(0[1-9]|[12][0-9]))T(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9]):(0[0-9]|[1-5][0-9])\.[0-9]{3}Z$

考虑到:

  • 正确的符号(' - ',' T ',':','。',' Z ')放在正确的位置。
  • 与每月29、30或31天的一致性。
  • 小时从00到23。
  • 分钟和秒钟从00到59。
  • 毫秒从000到999。

未考虑:

  • 闰年。

示例日期:2019-11-15T13:34:22.178Z

Example to run directly in Chrome console: /^[0-9]{4}-((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01])|(0[469]|11)-(0[1-9]|[12][0-9]|30)|(02)-(0[1-9]|[12][0-9]))T(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9]):(0[0-9]|[1-5][0-9])\.[0-9]{3}Z$/.test("2019-11-15T13:34:22.178Z"); Regex flow diagram (Regexper): regex flow diagram

为什么使用如此复杂的正则表达式,如果它不能验证闰年?也不能验证闰秒。 - Toto
@Toto 因为它是之前发布的所有选项中最好的。它尊重我在“考虑”部分中指出的每一项。尝试使用此答案中的正则表达式以及先前发布的其他正则表达式,使用此网站,您将看到我所说的内容。(我还评论了每个其他正则表达式中存在的问题,显示它们为什么有缺陷) - ferpel
抱歉,它还不够好,因为它无法处理闰年。 - Toto
这是更好的,因为其他的不兑现承诺。请尝试使用我在先前评论中建议的页面中的其他正则表达式答案,您将看到我试图解释的内容。 - ferpel
我认为这个解决方案(虽然它没有检查闰年和秒),但是,它可以简化为/^[1-9]\d{3}-((0[13578]|1[02])-([0-2]\d|31)|([469]|11)-([0-2]\d|31)|02-[0-2]\d)T([01](0-9]|2[0-3])):[0-5]\d\.\d{3}Z$/。请注意,我刚刚发现Node.js(使用node@19.0.1进行测试)仅支持1000年至9999年之间的年份,因此我相应地更新了正则表达式。Firefox Nightly web控制台也是如此。 - tukusejssirs

7
以下是一个用于检查 ISO 8601 日期格式(包括闰年和短长月)的正则表达式。为了运行它,您需要“忽略空格”。一个没有空格的压缩版本可以在 regexlib 上找到:http://regexlib.com/REDetails.aspx?regexp_id=3344 ISO 8601 还有更多内容 - 此正则表达式仅关注日期,但您可以轻松扩展它以支持时间验证,这并不那么棘手。
更新:现在使用 javascript(没有 lookbehinds)也可以工作。
  ^(?:
      (?=
            [02468][048]00
            |[13579][26]00
            |[0-9][0-9]0[48]
            |[0-9][0-9][2468][048]
            |[0-9][0-9][13579][26]              
      )

      \d{4}

      (?:

        (-|)

        (?:

            (?:
                00[1-9]
                |0[1-9][0-9]
                |[1-2][0-9][0-9]
                |3[0-5][0-9]
                |36[0-6]
            )
            |
                (?:01|03|05|07|08|10|12)
                (?:
                  \1
                  (?:0[1-9]|[12][0-9]|3[01])
                )?            
            |
                (?:04|06|09|11)
                (?:
                  \1
                  (?:0[1-9]|[12][0-9]|30)
                )?            
            |
                02
                (?:
                  \1
                  (?:0[1-9]|[12][0-9])
                )?

            |
                W(?:0[1-9]|[1-4][0-9]|5[0-3])
                (?:
                  \1
                  [1-7]
                )?

        )            
      )?
  )$
  |
  ^(?:
      (?!
            [02468][048]00
            |[13579][26]00
            |[0-9][0-9]0[48]
            |[0-9][0-9][2468][048]
            |[0-9][0-9][13579][26]              
      )

      \d{4}

      (?:

        (-|)

        (?:

            (?:
                00[1-9]
                |0[1-9][0-9]
                |[1-2][0-9][0-9]
                |3[0-5][0-9]
                |36[0-5]
            )
            |
                (?:01|03|05|07|08|10|12)
                (?:
                  \2
                  (?:0[1-9]|[12][0-9]|3[01])
                )?

            |
                (?:04|06|09|11)
                (?:
                  \2
                  (?:0[1-9]|[12][0-9]|30)
                )?
            |
                (?:02)
                (?:
                  \2
                  (?:0[1-9]|1[0-9]|2[0-8])
                )?
            |
                W(?:0[1-9]|[1-4][0-9]|5[0-3])
                (?:
                  \2
                  [1-7]
                )?
       ) 
    )?
)$

为了考虑时间因素,可以将以下内容添加到混合物中(来自:http://underground.infovark.com/2008/07/22/iso-date-validation-regex/):
([T\s](([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)?(\15([0-5]\d))?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?

4

ISO 8601规范允许使用多种日期格式。这里有一个中等水平的解释(点击此处)。对于不指定时区的简单日期,Javascript的日期输入格式和ISO格式之间存在一些较小的差异,可以使用字符串替换轻松解决。完全支持ISO-8601规范是非常困难的。

以下是一个参考示例,虽然它可以解析来自上述维基百科页面的非持续时间日期,但我不能保证其完整性。

下面是一个示例,您还可以在ideone上查看其输出。不幸的是,它无法按照规范工作,因为它没有正确实现周。 ISO-8601中第01周的定义是非常困难的,需要浏览日历以确定第一周从何处开始,以及它在指定年份的天数方面确切意味着什么。这可能可以相当容易地纠正(我只是厌倦了与它打交道)。

function parseISODate (input) {
    var iso = /^(\d{4})(?:-?W(\d+)(?:-?(\d+)D?)?|(?:-(\d+))?-(\d+))(?:[T ](\d+):(\d+)(?::(\d+)(?:\.(\d+))?)?)?(?:Z(-?\d*))?$/;

    var parts = input.match(iso);

    if (parts == null) {
        throw new Error("Invalid Date");
    }

    var year = Number(parts[1]);

    if (typeof parts[2] != "undefined") {
        /* Convert weeks to days, months 0 */
        var weeks = Number(parts[2]) - 1;
        var days  = Number(parts[3]);

        if (typeof days == "undefined") {
            days = 0;
        }

        days += weeks * 7;

        var months = 0;
    }
    else {
        if (typeof parts[4] != "undefined") {
            var months = Number(parts[4]) - 1;
        }
        else {
            /* it's an ordinal date... */
            var months = 0;
        }

        var days   = Number(parts[5]);
    }

    if (typeof parts[6] != "undefined" &&
        typeof parts[7] != "undefined")
    {
        var hours        = Number(parts[6]);
        var minutes      = Number(parts[7]);

        if (typeof parts[8] != "undefined") {
            var seconds      = Number(parts[8]);

            if (typeof parts[9] != "undefined") {
                var fractional   = Number(parts[9]);
                var milliseconds = fractional / 100;
            }
            else {
                var milliseconds = 0
            }
        }
        else {
            var seconds      = 0;
            var milliseconds = 0;
        }
    }
    else {
        var hours        = 0;
        var minutes      = 0;
        var seconds      = 0;
        var fractional   = 0;
        var milliseconds = 0;
    }

    if (typeof parts[10] != "undefined") {
        /* Timezone adjustment, offset the minutes appropriately */
        var localzone = -(new Date().getTimezoneOffset());
        var timezone  = parts[10] * 60;

        minutes = Number(minutes) + (timezone - localzone);
    }

    return new Date(year, months, days, hours, minutes, seconds, milliseconds);
}

print(parseISODate("2010-06-29T15:33:00Z-7"))
print(parseISODate("2010-06-29 06:14Z"))
print(parseISODate("2010-06-29T06:14Z"))
print(parseISODate("2010-06-29T06:14:30.2034Z"))
print(parseISODate("2010-W26-2"))
print(parseISODate("2010-180"))

4

yyyy-MM-dd

这里大多数答案都解释得太多了,以下是@Sergey的 答案 的简短变体,解决了一些奇怪的情况(例如2020-00-00),此 RegExp 只关心 yyyy-MM-dd 日期:

// yyyy-MM-dd
^\d{4}-([0][1-9]|1[0-2])-([0-2][1-9]|[1-3]0|3[01])$

另外,这个日期转换不考虑每个月的天数,例如 2020-11-31(因为11月只有30天)。

我的使用场景是将一个String转换为Date(来自API参数),我只需要知道输入字符串不包含奇怪的内容,然后对实际的Date对象进行下一步验证。


3
这是我的看法:

这里是我的观点:

^\d{4}-(?:0[1-9]|1[0-2])-(?:[0-2][1-9]|[1-3]0|3[01])T(?:[0-1][0-9]|2[0-3])(?::[0-6]\d)(?::[0-6]\d)?(?:\.\d{3})?(?:[+-][0-2]\d:[0-5]\d|Z)?$

匹配的例子:

2016-12-31T23:59:60+12:30
2021-05-10T09:05:12.000Z
3015-01-01T23:00+02:00
1001-01-31T23:59:59Z
2023-12-20T20:20

分钟和秒部分可以再精细化,但这对我来说已经足够好了。 Regexper enter image description here

1

使用QRegExp和IsoDateWithMs时,这里的毫秒不起作用。相反,以下方法解决了问题。

\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d{1,3}

(我知道这是一篇JS入门文章,但它出现在首位,对C++开发人员也很有帮助。)


1

不确定它是否与您试图解决的根本问题相关,但您可以将ISO日期字符串作为构造函数参数传递给Date(),并从中获取一个对象。构造函数在将字符串强制转换为日期方面实际上非常灵活。


1
确认,那种方法行不通。 问题出在IE上。2014-06-44会变成2014-08-13,因为IE将溢出的日期(>30)视为下个月的日期。我已经在IE8和IE11上尝试过了。但在Chrome中可以正常工作。非常令人恼火。 - mortb

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