完整的ISO 8601日期语法的正则表达式

4
更新:目前,我们已成功地使用多个正则表达式同时解决了问题。
我正在寻找一种正则表达式,它涵盖了ISO 8601日期可接受语法的范围。大多数库和正则表达式只覆盖ISO 8601的一个子集或“简化”版本,如RFC 3339,但完整的ISO 8601包括表达时间持续和间隔的方法。维基百科对ISO 8601的条目提供了很好的概述,但简而言之,以下所有内容都应该是有效的日期:
var testDates = {
    'year' : "2013",        
    'date' : "2013-01-05",
    'datetime' : "2013-01-05T04:13:00+00:00",
    'Duration only' : "P1Y2M10DT2H30M",
    'Week Duration' : "P1W",
    'Range with start and end' : "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z",
    'Range of Date/Duration' : "2007-03-01T13:00:00Z/P1Y2M10DT2H30M",
    'Range of Date/Duration 1 month' : "2012-10/P1M",
    'Range of Date/Duration 1 week' : "2012-10/P1W",
    'Range of Duration/Date' : "P1Y2M10DT2H30M/2007-03-01T13:00:00Z", 
    'Repeating interval 5 times' : "R5/2007-03-01T13:00:00Z/P1Y2M10DT2H30M", 
    'Repeating interval weekly indefinitely' : "R/2012-10/P1W",
    'Repeating interval monthly 5 times' : "R5/2012-10/P1M"
}

我正在尝试构建一个正则表达式,以涵盖所有这些可能性。我已经组合了一些组件,但是我对正则表达式的技能不足,无法将它们作为一个表达式工作,并覆盖所有可能的情况。仅简单地在这些表达式之间进行“OR”似乎不能按预期工作,但也许我没有做得完全正确。
我尝试的表达式包括以下内容:
var regex = {
    'Date' : /^([\+-]?\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)?)?)?)?$/,
    'Duration' : /^P(?=\w*\d)(?:\d+Y|Y)?(?:\d+M|M)?(?:\d+W|W)?(?:\d+D|D)?(?:T(?:\d+H|H)?(?:\d+M|M)?(?:\d+(?:\­.\d{1,2})?S|S)?)?$/,      
    'Range of Date/Date' : /^([\+-]?\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)?)?)?)?(\/)([\+-]?\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)?)?)?)?$/,      
    'Range of Date/Duration' : /^([\+-]?\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)?)?)?)?(\/)P(?=\w*\d)(?:\d+Y|Y)?(?:\d+M|M)?(?:\d+W|W)?(?:\d+D|D)?(?:T(?:\d+H|H)?(?:\d+M|M)?(?:\d+(?:\­.\d{1,2})?S|S)?)?$/,
    'Range of Duration/Date' : /^P(?=\w*\d)(?:\d+Y|Y)?(?:\d+M|M)?(?:\d+W|W)?(?:\d+D|D)?(?:T(?:\d+H|H)?(?:\d+M|M)?(?:\d+(?:\­.\d{1,2})?S|S)?)?\/([\+-]?\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)?)?)?)?$/,
    'Repeating interval' : /^R\d*\/([\+-]?\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)?)?)?)?\/P(?=\w*\d)(?:\d+Y|Y)?(?:\d+M|M)?(?:\d+W|W)?(?:\d+D|D)?(?:T(?:\d+H|H)?(?:\d+M|M)?(?:\d+(?:\­.\d{1,2})?S|S)?)?$/
}   

我已经编写了一个示例脚本,尝试对上述列出的每个日期运用这些表达式,但如果有人能够指导如何将所有这些情况组合到一个表达式中,那将非常感激。
推动这一使用案例的是验证JSON模式文档的ISO 8601日期。 JSON Schema在验证内容方面提供了一定的灵活性,您可以提供多个规则或正则表达式进行测试。在这种情况下,我可以通过使用多个正则表达式而不是一个组合表达式来解决我的问题。
更新:目前已经通过使用这种方法(同时使用多个单独的表达式)解决了问题。
以下是脚本:

有趣的问题,我很好奇看到你的最终答案。然而,我强烈建议使用自由间距模式、缩进和注释编写这些(非平凡)正则表达式,以便普通人可以阅读(即使JavaScript不支持x-modifier)。在JS的无x-mode限制下,我喜欢在实际的RegExp文字之前立即包含多行注释中的冗长正则表达式。 - ridgerunner
我已更新描述,指出所有这些表达式都只是两个表达式(一个用于日期,一个用于持续时间)的不同组合,目前只有一种组合无法正常工作。我还更新了要点,提到了这两个表达式的来源,并在那里提供了一些额外的解释,说明它们是如何构建的。 - Philip Ashlock
3个回答

5
这是我找到的一整套表达式,可用于实现多样化的ISO 8601语法。这个代码片段已更新了这些表达式,并添加了对重复间隔的附加测试。
var regex = {
    'Date' : /^([\+-]?\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)?)?)?)?$/,
    'Duration' : /^P(?=\w*\d)(?:\d+Y|Y)?(?:\d+M|M)?(?:\d+W|W)?(?:\d+D|D)?(?:T(?:\d+H|H)?(?:\d+M|M)?(?:\d+(?:\­.\d{1,2})?S|S)?)?$/,      
    'Range of Date/Date' : /^([\+-]?\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)?)?)?)?(\/)([\+-]?\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)?)?)?)?$/,      
    'Range of Date/Duration' : /^([\+-]?\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)?)?)?)?(\/)P(?=\w*\d)(?:\d+Y|Y)?(?:\d+M|M)?(?:\d+W|W)?(?:\d+D|D)?(?:T(?:\d+H|H)?(?:\d+M|M)?(?:\d+(?:\­.\d{1,2})?S|S)?)?$/,
    'Range of Duration/Date' : /^P(?=\w*\d)(?:\d+Y|Y)?(?:\d+M|M)?(?:\d+W|W)?(?:\d+D|D)?(?:T(?:\d+H|H)?(?:\d+M|M)?(?:\d+(?:\­.\d{1,2})?S|S)?)?\/([\+-]?\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)?)?)?)?$/,
    'Repeating interval' : /^R\d*\/([\+-]?\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)?)?)?)?\/P(?=\w*\d)(?:\d+Y|Y)?(?:\d+M|M)?(?:\d+W|W)?(?:\d+D|D)?(?:T(?:\d+H|H)?(?:\d+M|M)?(?:\d+(?:\­.\d{1,2})?S|S)?)?$/
}

一种修改后的日期正则表达式,它可以严格捕获解析日期所需的内容。请注意,“24:00”未被捕获,但实际上它与没有时间是一样的,因此任何解析程序都应该以相同的方式处理它。/^([\+-]?\d{4}(?!\d{2}\b))(?:(-?)(?:(0[1-9]|1[0-2])(?:\2([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+(?!:))?)?(?:\10([0-5]\d)([\.,]\d+)?)?([zZ]|([\+-](?:[01]\d|2[0-3])):?([0-5]\d)?)?)?)?$/ - Adam Leggett

1

请允许我对整个方法提出疑问。日期、时间和范围是不同的概念,如果将它们交替使用(我从想要一个正则表达式适用于它们所有的意图中了解到这一点),将导致用户、程序员或(更糟糕的是)两者都出错。

如果允许的格式过于复杂,某人会犯错误。你的应用程序不能只使用其中的一部分吗?


0

对于这个问题,正则表达式是最好/最适合的解决方案吗?

看起来你真的在尝试根据规范解析字符串。只需看看你尝试过的一些正则表达式有多复杂。

我的建议是要么制作简单的正则表达式来匹配每个模式,夹杂一些 if/then 代码,要么使用/创建全面的解析器。

请记住,正则表达式是为模式匹配而设计的,而不是解析。它们非常适合从字符串中快速提取值,但不适用于更复杂的事情。


我实际上并不想解析它,只是想验证它。这是在 JSON Schema 文档 (http://json-schema.org/) 中使用的,因此它旨在使用正则表达式来实现语言无关性。 - Philip Ashlock
在这种情况下,我会为每个模式创建一个正则表达式,并告诉读者应用所有这些正则表达式,而不是一个试图覆盖所有模式且难以阅读的超级表达式。除了认为您拥有一个可以完成所有工作的超级正则表达式之外,单个正则表达式能给您带来什么?使用6个更小且易于理解的正则表达式呢? - Twisted Mentat
幸运的是,JSON Schema确实允许您定义多个可能的验证规则,因此我认为在JSON Schema的上下文中可以实现这一点。在这种情况下,我认为我唯一缺少的正则表达式是能够在范围内正确组合日期和持续时间的正则表达式。我会努力解决这个问题,并在这里提供更新。 - Philip Ashlock
我已经更新了描述和要点,建议采用这种方法。目前,我只缺少一个有效的正则表达式来匹配两个组合表达式之一(包含范围和持续时间/日期)。 - Philip Ashlock
2
你应该意识到,即使使用那些复杂的正则表达式,你仍然会得到错误的匹配结果。 2014-02-31 不是一个有效的日期,但它也会匹配成功,同样的情况也适用于 2014-04-31 - Arjan
没问题,主要重点是验证ISO 8601语法。 - Philip Ashlock

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