匹配有效日期的正则表达式

80

我正在尝试编写一个正则表达式来验证日期。该正则表达式需要匹配以下内容:

  • M/D/YYYY
  • MM/DD/YYYY
  • 单个数字的月份可以以前导零开头(例如:03/12/2008)
  • 单个数字的日期可以以前导零开头(例如:3/02/2008)
  • 不能包括2月30日或2月31日(例如:2/31/2008)

到目前为止,我的正则表达式是:

^(([1-9]|1[012])[-/.]([1-9]|[12][0-9]|3[01])[-/.](19|20)\d\d)|((1[012]|0[1-9])(3[01]|2\d|1\d|0[1-9])(19|20)\d\d)|((1[012]|0[1-9])[-/.](3[01]|2\d|1\d|0[1-9])[-/.](19|20)\d\d)$

除了仍包括2/30/2008和2/31/2008之外,这个匹配是正确的。

有没有更好的建议?

编辑:我在RegExLib上找到了答案

^((((0[13578])|([13578])|(1[02]))[\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\/](([1-9])|([0-2][0-9]))))[\/]\d{4}$|^\d{4}$

它匹配符合 MM/DD/YYYY 格式的所有有效月份。
感谢大家的帮助。

3
此程序未考虑闰年,会将02/29/2011输出为一个有效日期。 - Varun Achar
2
请检查我的答案,其中包括考虑闰年的正则表达式。 - Varun Achar
它匹配所有遵循MM/DD/YYYY格式的有效月份。无法验证“1234”!:( - Aritra B
16个回答

158

这不是正则表达式的恰当使用。最好使用其他方法。

[0-9]{2}/[0-9]{2}/[0-9]{4}

然后在高级语言中检查范围。


7
这个正则表达式不正确,因为它只检查月/日/年中数字的数量。 - Sanjeev Singh
2
同意,这就像使用一个检查所有可能区号的电话号码正则表达式。如果你包括非闰年的2/29和4/31、6/31、9/31和11/31,那么不包括2/30或2/31有什么意义呢? - Jason Goemaat
17
@SanjeevSingh 这就是关键所在 - 正则表达式不应用于数据验证。这将匹配类似日期的字符串,如果需要,可以使用适当的日期库进行验证。 - dimo414
@Chris 或许你可以在你的答案开头加上^,在结尾加上$以匹配整个日期字符串。(顺便说一下:我遵循了你的建议,在代码中实现闰年等检查。是的:那绝对比你说的好)。 - primehunter
^(([0-9]{0,2})( "separator" )?){0,2}[1-2]?([0-9]{0,3}) - Pekee

64

这是一个可以匹配所有有效日期(包括闰年)的正则表达式。可接受的格式为mm/dd/yyyy或mm-dd-yyyy或mm.dd.yyyy格式。

^(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[1,3-9]|1[0-2])(\/|-|\.)(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:0?[1-9]|1\d|2[0-8])\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$

来源:Asiq Ahamed


9
公元前20年怎么办?(例如“-20/1/1”) - Odys
26
@Odys - 你实际上需要为某事编写那段程序吗,还是你只是凭空指责? - Dan Nissenbaum
4
是的,在我发表评论的时候(两年前),我需要表示早期的日期,甚至更久远的日期。 - Odys
如果我想检查日期格式,例如DDMMYYYY,请帮我实现。 - Himanshu N Tatariya
@HimanshuNTatariya DDMMYYYY /(((0[1-9]|[12][0-9]|3[01])(0[13578]|10|12)(\d{4}))|(([0][1-9]|[12][0-9]|30)(0[469]|11)(\d{4}))|((0[1-9]|1[0-9]|2[0-8])(02)(\d{4}))|((29)(02)([02468][048]00))|((29)(02)([13579][26]00))|((29)(02)([0-9][0-9][0][48]))|((29)(02)([0-9][0-9][2468][048]))|((29)(02)([0-9][0-9][13579][26])))/ - Frederik Krautwald
显示剩余3条评论

42

因为这个问题的标题比较广泛,我在寻找一个正则表达式来匹配特定的日期格式(就像OP一样)时才来到这里。但是我发现,正如许多答案和评论全面强调的那样,当提取与质量较差或非结构化源数据混合的日期时,构建有效模式非常棘手。

在探索这个问题时,我想出了一个系统,使您可以通过将四个简单的子表达式按照所需顺序排列在一起来构建正则表达式,以匹配分隔符以及年、月和日字段的有效范围。

它们是:

分隔符

[^\w\d\r\n:] 

这将匹配任何不是单词字符、数字字符、回车、换行或冒号的内容。冒号必须存在,以避免匹配看起来像日期的时间(参见我的测试数据)。

您可以优化此部分模式以加快匹配速度,但这是一个检测大多数有效分隔符的良好基础。

但请注意; 它将匹配具有混合分隔符的字符串,例如 2/12-73,这可能实际上不是有效日期。

年份值

(\d{4}|\d{2})

这匹配两个或四个数字组成的一组,大多数情况下这是可以接受的,但如果您处理的数据来自公元0年至999年或超过9999年,您需要决定如何处理,因为大多数情况下1、3或>4位数字年份都是无用的。

月份值

(0?[1-9]|1[0-2])

匹配1到12之间的任何数字,可以有前导0 - 注意:0和00不匹配。

日期数值

(0?[1-9]|[12]\d|30|31)

匹配1到31之间的任何数字,可以有或没有前导零 - 注意:不匹配0和00。

此表达式匹配以日期、月份和年份格式化的日期

(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})

但它也会匹配一些年月日格式。应该用边界操作符包围它以确保选择整个日期字符串并防止从未经过良好形式化处理的数据中提取有效子日期,即没有边界标签的数据,例如 20/12/194 匹配为 20/12/19,101/12/1974 匹配为 01/12/1974。

将下面无意义部分的测试数据与上面的表达式结果进行比较

\b(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})\b
此正则表达式没有进行验证,因此一个格式正确但无效的日期(例如31/02/2001)也会被匹配。这是一个数据质量问题,正如其他人所说,您的正则表达式不应该需要验证数据。
由于(作为开发者)您无法保证源数据的质量,因此您需要在代码中执行和处理额外的验证。如果您尝试在正则表达式中匹配并且验证数据,它将变得非常混乱,并且难以在没有非常精确文档的情况下支持。
垃圾进,垃圾出。
话虽如此,如果您确实拥有日期值不同的混合格式,并且必须尽可能地提取,则可以像下面这样组合几个表达式; 此(灾难性的)表达式匹配DMY和YMD日期。
(\b(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})\b)|(\b(0?[1-9]|1[0-2])[^\w\d\r\n:](0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](\d{4}|\d{2})\b)

但是你无法确定像6/9/1973这样的日期是九月六日还是六月九日。我很难想象在何种情况下不会因此导致问题,这是不良行为,你不应该这样处理——找到数据所有者并用治理锤打击他们。

最后,如果你想匹配没有分隔符的YYYYMMDD字符串,你可以减少一些不确定性,表达式看起来像这样。

\b(\d{4})(0[1-9]|1[0-2])(0[1-9]|[12]\d|30|31)\b

需要注意的是,它将匹配形式良好但无效的值,例如20010231(2月31日!):)

测试数据

在尝试本主题中的解决方案时,我得到了一个包含各种有效和无效日期以及一些棘手情况的测试数据集,您可能希望或不希望匹配,例如可能与日期匹配的时间和跨多行的日期。

我希望这对某人有用。

Valid Dates in various formats

Day, month, year
2/11/73
02/11/1973
2/1/73
02/01/73
31/1/1973
02/1/1973
31.1.2011
31-1-2001
29/2/1973
29/02/1976 
03/06/2010
12/6/90

month, day, year
02/24/1975 
06/19/66 
03.31.1991
2.29.2003
02-29-55
03-13-55
03-13-1955
12\24\1974
12\30\1974
1\31\1974
03/31/2001
01/21/2001
12/13/2001

Match both DMY and MDY
12/12/1978
6/6/78
06/6/1978
6/06/1978

using whitespace as a delimiter

13 11 2001
11 13 2001
11 13 01 
13 11 01
1 1 01
1 1 2001

Year Month Day order
76/02/02
1976/02/29
1976/2/13
76/09/31

YYYYMMDD sortable format
19741213
19750101

Valid dates before Epoch
12/1/10
12/01/660
12/01/00
12/01/0000

Valid date after 2038

01/01/2039
01/01/39

Valid date beyond the year 9999

01/01/10000

Dates with leading or trailing characters

12/31/21/
31/12/1921AD
31/12/1921.10:55
12/10/2016  8:26:00.39
wfuwdf12/11/74iuhwf
fwefew13/11/1974
01/12/1974vdwdfwe
01/01/99werwer
12321301/01/99

Times that look like dates

12:13:56
13:12:01
1:12:01PM
1:12:01 AM

Dates that runs across two lines

1/12/19
74

01/12/19
74/13/1946

31/12/20
08:13

Invalid, corrupted or nonsense dates

0/1/2001
1/0/2001
00/01/2100
01/0/2001
0101/2001
01/131/2001
31/31/2001
101/12/1974
56/56/56
00/00/0000
0/0/1999
12/01/0
12/10/-100
74/2/29
12/32/45
20/12/194

2/12-73

3
非常好的解释,附有例子!可以考虑添加其他月份格式,例如MMM和全名月份正则表达式! - AVA
谢谢!“disastrous”表达式有错误吗?我无法将其与yyyy-mm-dd格式的日期匹配,不得不将其更改为(在Perl中):/((\b(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})\b)|(\d{4}|\d{2})[^\w\d\r\n:](\b(0?[1-9]|1[0-2])[^\w\d\r\n:](0?[1-9]|[12]\d|30|31)\b))/x' --- 测试为:echo 2017-01-28 | perl -ne 'print "$1\n" if /((\b(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})\b)|(\d{4}|\d{2})[^\w\d\r\n:](\b(0?[1-9]|1[0-2])[^\w\d\r\n:](0?[1-9]|[12]\d|30|31)\b))/' - cxw
是的,不使用正则表达式进行验证非常有用。KISS(保持简单愚蠢!)是在这种情况下必须遵循的另一个原则。 很棒的文档/描述,@Bob! - LucianDex

14

易于维护的 Perl 5.10 版本

/
  (?:
      (?<month> (?&mon_29)) [\/] (?<day>(?&day_29))
    | (?<month> (?&mon_30)) [\/] (?<day>(?&day_30))
    | (?<month> (?&mon_31)) [\/] (?<day>(?&day_31))
  )
  [\/]
  (?<year> [0-9]{4})
  
  (?(DEFINE)
    (?<mon_29> 0?2 )
    (?<mon_30> 0?[469]   | (11) )
    (?<mon_31> 0?[13578] | 1[02] )

    (?<day_29> 0?[1-9] | [1-2]?[0-9] )
    (?<day_30> 0?[1-9] | [1-2]?[0-9] | 30 )
    (?<day_31> 0?[1-9] | [1-2]?[0-9] | 3[01] )
  )
/x
你可以通过名称在这个版本中检索元素。
say "Month=$+{month} Day=$+{day} Year=$+{year}";

(没有尝试限制年份的值。)


这会不会匹配“12/00/0000”? - mwolfetech
@mwolfetech 大多数其他情况也是如此,如果您需要检查它,应该很容易弄清楚如何修改这个正则表达式。 - Brad Gilbert
有一个实际可维护的版本,加一分。 - Mike H-R

9

为了控制日期的有效性,需要遵循以下格式:

YYYY/MM/DD或YYYY-MM-DD

我建议您使用以下正则表达式:

(((19|20)([2468][048]|[13579][26]|0[48])|2000)[/-]02[/-]29|((19|20)[0-9]{2}[/-](0[4678]|1[02])[/-](0[1-9]|[12][0-9]|30)|(19|20)[0-9]{2}[/-](0[1359]|11)[/-](0[1-9]|[12][0-9]|3[01])|(19|20)[0-9]{2}[/-]02[/-](0[1-9]|1[0-9]|2[0-8])))

匹配

2016年02月29日 | 2012年04月30日 | 2019年09月31日

不匹配

2016年02月30日 | 2012年04月31日 | 2019年09月35日

如果您想只允许'/'或'-'分隔符,可以自定义它。这个正则表达式严格控制日期的有效性,并验证28、30和31天的月份,甚至包括闰年的29/02月。

试一试,它非常有效,可以防止您的代码出现很多错误!

顺便说一下:我为SQL datetime做了一个变体。你可以在这里找到它(搜索我的名字):正则表达式验证时间戳

欢迎反馈 :)


4
听起来你正在过度使用正则表达式。我会使用一个正则表达式来匹配几个日期格式,然后使用一个单独的函数来验证所提取的日期字段的值。

3

Perl扩展版

请注意使用/x修改器。

/^(
      (
        ( # 31 day months
            (0[13578])
          | ([13578])
          | (1[02])
        )
        [\/]
        (
            ([1-9])
          | ([0-2][0-9])
          | (3[01])
        )
      )
    | (
        ( # 30 day months
            (0[469])
          | ([469])
          | (11)
        )
        [\/]
        (
            ([1-9])
          | ([0-2][0-9])
          | (30)
        )
      )
    | ( # 29 day month (Feb)
        (2|02)
        [\/]
        (
            ([1-9])
          | ([0-2][0-9])
        )
      )
    )
    [\/]
    # year
    \d{4}$
  
  | ^\d{4}$ # year only
/x

Original

^((((0[13578])|([13578])|(1[02]))[\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\/](([1-9])|([0-2][0-9]))))[\/]\d{4}$|^\d{4}$

3

如果你尝试了以上建议仍然无法解决问题,我使用以下方法获取日期。这个表达式可以通过50个链接获取每个页面上的所有日期。

^20\d\d-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(0[1-9]|[1-2][0-9]|3[01])$ 

3

这个正则表达式用于验证日期格式为01-01-2000到12-31-2099之间的日期,并匹配分隔符。

^(0[1-9]|1[012])([- /.])(0[1-9]|[12][0-9]|3[01])\2(19|20)\d\d$

2
    var dtRegex = new RegExp(/[1-9\-]{4}[0-9\-]{2}[0-9\-]{2}/);
    if(dtRegex.test(date) == true){
        var evalDate = date.split('-');
        if(evalDate[0] != '0000' && evalDate[1] != '00' && evalDate[2] != '00'){
            return true;
        }
    }

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