匹配日期的正则表达式

17
我希望能够匹配格式为mm/dd/yy或mm/dd/yyyy的日期,但不应该选择月份无效的23/09/2010,也不应该选择一些无效的日期,如00/12/2020或12/00/2011。


这不是一项容易的任务(虽然可能是可能的)。你需要在正则表达式中处理闰年才能实现。 - sawa
@sawa 非闰世纪,除了 % 400 闰世纪。 - Phrogz
7个回答

43

相较于一个非常庞大的正则表达式(假设这是用于验证而不是扫描),以下方法更好:

require 'date'
def valid_date?( str, format="%m/%d/%Y" )
  Date.strptime(str,format) rescue false
end

补充一句:呃!为什么要使用如此糟糕的日期格式?选择国际标准的ISO8601,即YYYY-MM-DD,它有一个一致的部分排序和字典序排序。


1
正则表达式非常适合提取部分内容,但对于验证范围来说并不理想。是的,ISO8601格式可以解决很多问题。 - the Tin Man
2
我早些时候读过这篇文章,当时并不真正理解为什么字典序排序是一件好事。它可以让你使用字符串比较来对日期进行排序或查找最低/最高日期!例如 '2014-01-01 > '2013-12-12' = true。如果使用 mm/dd/yy 或类似的格式,这种方法将失败。 - Subtletree
抱歉,但是如 @Simon-Woker 所说,问题要求使用正则表达式而不是通用解决方案来验证日期,因此对我来说是 -1。 - Filippo1980
@Filippo1980 谢谢您的解释。这是一个合理的降低投票的原因。 - Phrogz

26

最好对斜杠 / 进行拆分,并测试每个单独的部分。但如果你真的想要使用正则表达式,你可以尝试这个:

#\A(?:(?:(?:(?:0?[13578])|(1[02]))/31/(19|20)?\d\d)|(?:(?:(?:0?[13-9])|(?:1[0-2]))/(?:29|30)/(?:19|20)?\d\d)|(?:0?2/29/(?:19|20)(?:(?:[02468][048])|(?:[13579][26])))|(?:(?:(?:0?[1-9])|(?:1[0-2]))/(?:(?:0?[1-9])|(?:1\d)|(?:2[0-8]))/(?:19|20)?\d\d))\Z#

解释:

\A           # start of string
 (?:         # group without capture
             # that match 31st of month 1,3,5,7,8,10,12
   (?:       # group without capture
     (?:     # group without capture
       (?:   # group without capture
         0?  # number 0 optionnal
         [13578] # one digit either 1,3,5,7 or 8
       )     # end group
       |     # alternative
       (1[02]) # 1 followed by 0 or 2
     )       # end group
     /       # slash
     31      # number 31
     /       # slash
     (19|20)? #numbers 19 or 20 optionnal
     \d\d    # 2 digits from 00 to 99 
   )         # end group
|
   (?:(?:(?:0?[13-9])|(?:1[0-2]))/(?:29|30)/(?:19|20)?\d\d)
|
   (?:0?2/29/(?:19|20)(?:(?:[02468][048])|(?:[13579][26])))
|
   (?:(?:(?:0?[1-9])|(?:1[0-2]))/(?:(?:0?[1-9])|(?:1\d)|(?:2[0-8]))/(?:19|20)?\d\d)
 )
\Z

我已经解释了第一个部分,把剩下的部分作为练习留给你。

这个匹配项是针对除了 1900 年 02 月 29 日之外的任何日期,而该日期是无效的。


1
+1 建议使用 split 方法。在这种情况下,它是最简单的方法。 - kikito
@egarcia:谢谢。当然,这样做肯定更好,也更易读。 - Toto
如果您要锚定正则表达式以进行单字符串验证,则应使用\A\z而不是^$ - Phrogz
@Phrogz:这取决于正则表达式的风格。但是,对于Ruby来说,你是正确的。 - Toto
如果用户根本没有输入斜杠怎么办?split方法将无法工作。 - E.E.33
显示剩余2条评论

8

这太棒了,感谢您用正则表达式解决问题,为我节省了很多时间。 - bobmagoo
2
如果您有未验证的字符串,请小心。例如,使用邮政编码会产生不良结果。DateTime.parse('60201-4286').to_s返回"2060-07-19T00:00:00+00:00"而且不会失败。 - dev_row
@Simon-Woker:我对你的回答投-1票,因为问题要求一个正则表达式而不是验证日期的解决方案。所以,很抱歉,对我来说,这可能是一个好答案。 - Filippo1980
DateTime.parse('monoxide') => #<DateTime: 2019-11-25T00:00:00+00:00 ((2458813j,0s,0n),+0s,2299161j)> - Jan Krupa

4

使用正则表达式最好的方法是验证格式,例如:

[0-1][0-9]/[0-3][0-9]/[0-9]{2}(?:[0-9]{2})?

除此之外,如果没有某种日期字典,就无法可靠地完成更多操作。例如,一个日期的有效性取决于它是否是闰年。


正如其他答案所示,这是完全不正确的。尽管正则表达式变得丑陋和难以控制,但您可以将匹配的有效性与某个任意级别的正确性相匹配。 - Phrogz
2
@Phrogz。这几乎是真的。正解是错误的,正如M42所指出的那样;它没有正确处理闰年。为了做到这一点,它必须纳入关于转换到公历等信息。然后,正则表达式将会很混乱。 - sawa
我显然有偏见,但+1 sawa。;-) - Denis de Bernardy

2
"

对于MM-DD-YYYY,您可以使用以下正则表达式。它适用于闰年,并且仅匹配正确的日期,除非年份超过2099。

"
(?:(09|04|06|11)(\/|-|\.)(0[1-9]|[12]\d|30)(\/|-|\.)((?:19|20)\d\d))|(?:(01|03|05|07|08|10|12)(\/|-|\.)(0[1-9]|[12]\d|3[01])(\/|-|\.)((?:19|20)\d\d))|(?:02(\/|-|\.)(?:(?:(0[1-9]|1\d|2[0-8])(\/|-|\.)((?:19|20)\d\d))|(?:(29)(\/|-|\.)((?:(?:19|20)(?:04|08|12|16|20|24|28|32|36|40|44|48|52|56|60|64|68|72|76|80|84|88|92|96))|2000))))

查看http://regexr.com/中的匹配项


这个正则表达式是做什么用的?我们需要相信你还是你能解释一下? - luk2302
你可以相信我。 :) 对于初学者来说,这确保了你不能在九月、四月、六月和十一月添加“31”。2月29日只能在闰年中添加,并且只适用于1900-2099年之间的年份。请告诉我这是否有帮助。 - Jaison Joy

0

这是你可以使用的代码 :), 试一下并告诉我:

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


你试过了吗?它匹配的是 00/00/0000,不确定它是否是一个有效的日期!它也匹配了 31/02/200031/06/2018 但不匹配 06/06/18。此外,OP想要的日期格式是 mm/dd/yymm/dd/yyyy - Toto

0

所以你想要一个能匹配 mm/dd/yy 格式的正则表达式

^((0?1?1){1}|(0?1?2){1}|([0]?3|4|5|6|7|8|9))\/((0?1?2?3?1){1}|(0?1?2?(2|3|4|5|6|7|8|9|0))|(30))\/[1-90]{4}$

这个正则表达式将精确匹配 mm/dd/yy 格式的内容,并且不会验证任何虚假日期。您可以在 regex101 上测试该正则表达式,以测试日期格式为 12/30/2040 和 09/09/2020 的内容,以及其他符合该格式的日期。我认为这也是您可以找到的最短的针对该格式的正则表达式。


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