正则表达式 | 闰年等技术问题

12

我最近一直在寻找一个正则表达式,用于客户端日期检查,但我找不到一个能满足以下标准的正则表达式:

  • 范围为1800年至今
  • 进行闰年的正确日期检查
  • 格式为MM/DD/YYYY
  • 无效日期检查

(这些限制超出了我的范围,并且是客户的要求,尽管我努力说服他们这不是最佳路径)

当前代码:

$('input').keyup(function()
{
       var regex = /^(?:(0[1-9]|1[012])[\/.](0[1-9]|[12][0-9]|3[01])[\/.](18|19|20)[0-9]{2})$/;
       $(this).toggleClass('invalid',!regex.test($(this).val()));    
});

更新:

需要说明的是,我主要是想看看这样的正则表达式是否可能(因为在这个问题中使用正则表达式并不是我的选择)。我知道还有其他(更好的)选项来验证日期,但正如之前提到的那样,我想看看是否可以通过正则表达式实现。


3
为什么你会使用正则表达式而不是Date类? - Demian Brecht
1
在非闰年里2月30日是合法的吗?;-P - Álvaro González
1
一个可以验证包括闰年在内的日期的正则表达式?那真是让我印象深刻啊... - Šime Vidas
@ŠimeVidas,我希望我给你留下了深刻的印象?我最初的三倍长度的猜测是目光短浅的,实际上接近于原始长度的5倍。 - McKay
1
是的,这不是一个“有用”的练习。 - McKay
显示剩余7条评论
13个回答

30

正如其他地方提到的那样,正则表达式几乎肯定不是您想要的。但是,话虽如此,如果您真的需要一个正则表达式,这就是它的构建方式:

31天月份

(0[13578]|1[02])[\/.](0[1-9]|[12][0-9]|3[01])[\/.](18|19|20)[0-9]{2}

30天的月份

(0[469]|11)[\/.](0[1-9]|[12][0-9]|30)[\/.](18|19|20)[0-9]{2}

2月1日至28日始终有效

(02)[\/.](0[1-9]|1[0-9]|2[0-8])[\/.](18|19|20)[0-9]{2}

闰年的2月29日也是合法的。

(02)[\/.]29[\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000)

这意味着如果你把所有东西都放在一起,它将是这样的:

((0[13578]|1[02])[\/.](0[1-9]|[12][0-9]|3[01])[\/.](18|19|20)[0-9]{2})|((0[469]|11)[\/.](0[1-9]|[12][0-9]|30)[\/.](18|19|20)[0-9]{2})|((02)[\/.](0[1-9]|1[0-9]|2[0-8])[\/.](18|19|20)[0-9]{2})|((02)[\/.]29[\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000))

这个版本稍微短一些,但是理解起来可能会有一点困难。

((0[13578]|1[02])[\/.]31[\/.](18|19|20)[0-9]{2})|((01|0[3-9]|1[1-2])[\/.](29|30)[\/.](18|19|20)[0-9]{2})|((0[1-9]|1[0-2])[\/.](0[1-9]|1[0-9]|2[0-8])[\/.](18|19|20)[0-9]{2})|((02)[\/.]29[\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000))

这些脚本很长且难以维护。显然这不是个好主意,但仍然有可能实现。

注意事项:

  • 年份范围为 1800-2099(可以方便地添加更多范围,但需要在 4-6 个不同的地方进行更改)
  • 要求月份和日期必须是两位数字(可以在 ~8 个地方放宽限制)
  • [\/.] 作为分隔符(8 个地方)
  • 没有经过测试(我们可以检查所有数字组合并与 JavaScript 的日期函数进行比较吗?[证明我们正在重新发明轮子])

1
"30天月份"部分多了一个括号,因此例如11/30/2012将不匹配,也不会匹配30天月份中的任何第30天日期。 - ErikE
为什么你不将1800年和1900年考虑为闰年?应该这样写:((18|19|20)(00|04|08|[2468][048]|[13579][26])) - despot
此外,如果有人感兴趣,如果您想将领先的0变为可选,则应将前导的“0”转换为“0?”。 - despot
2
@despot - 1800年和1900年不是闰年。再造轮子的另一个原因是,您可能不了解实际作用力的复杂性。 - McKay
1
即使您想将1800年和1900年视为闰年,闰年部分也可以简化为((18|19|20)([02468][048]|[13579][26]))。 十位数和个位数的长度必须是此示例中十位数和个位数的两倍左右,因为我必须拆分十位数中的零,因为1800年和1900年有特殊规则,不是闰年。 - McKay
显示剩余18条评论

13

我建议你放弃使用正则表达式来处理这个问题。最好将日期解析为其组成部分(月份、日期、年份),然后使用数字比较确保日期在正确的范围内。

更好的方法是尝试使用 Javascript Date.parse 函数来实现你想要的功能。

使用正则表达式解析日期是可能的,但非常令人沮丧。很难做到完全正确,对于不精通正则表达式的人来说理解起来也很困难(这意味着很难证明它是正确的),而且与其他选项相比速度也比较慢。


7
这是我的做法:
function validate( input ) {
    var date = new Date( input );
    input = input.split( '/' );   
    return date.getMonth() + 1 === +input[0] && 
           date.getDate() === +input[1] && 
           date.getFullYear() === +input[2];
}

使用方法:

validate( '2/1/1983' ) // true
validate( '2/29/1983' ) // false
validate( '2/29/1984' ) // true (1984 is a leap year)

Live demo: http://jsfiddle.net/9QNRx/


2

这个正则表达式是用于匹配 YYYY-MM-DD 格式的日期。

((18|19|20)[0-9]{2}[\-.](0[13578]|1[02])[\-.](0[1-9]|[12][0-9]|3[01]))|(18|19|20)[0-9]{2}[\-.](0[469]|11)[\-.](0[1-9]|[12][0-9]|30)|(18|19|20)[0-9]{2}[\-.](02)[\-.](0[1-9]|1[0-9]|2[0-8])|(((18|19|20)(04|08|[2468][048]|[13579][26]))|2000)[\-.](02)[\-.]29

为了帮助 OP 解决当前的问题,可以在答案中添加一些解释说明。 - ρяσѕρєя K
不会有一个好的答案,因为这甚至不接近可行的解决方案。 - Michael Gaskill

2
显然,正则表达式并不是这样做的理想方式。另外,最好使用“YYYY-MM-DD”(ISO 8601)格式而不是“MM/DD/YYYY”格式来工作,这样更安全。
话虽如此,以下是适用于1800年1月1日至2099年12月31日日期的最短完整工作正则表达式:
^(((0[1-9]|1[012])\/(?!00|29)([012]\d)|(0[13-9]|1[012])\/(29|30)|(0[13578]|1[02])\/31)\/(18|19|20)\d{2}|02\/29\/((18|19|20)(0[48]|[2468][048]|[13579][26])|2000))$

长度:162个字符。
细分:
^ # start
  (
    ( # non-leap months & days
      (0[1-9]|1[012])/(?!00|29)([012]\\d) # all months, days 01-28, uses negative lookahead
    |
      (0[13-9]|1[012])/(29|30) # all months except feb, days 29,30
    |
      (0[13578]|1[02])/31 # all 31 day months, day 31 only
    )
    /
    (18|19|20)\\d{2} # all years
  |
    02/29 # leap day
    /
    (
      (18|19|20)(0[48]|[2468][048]|[13579][26]) # leap years not divisible by 100
    |
      2000 # leap years divisible by 100
    )
  )
$ # end

这是一段英语文本,大意为:“这里有一个小提琴,可以测试从1800年到2099年的所有用例。此外,为了更加有趣,这里还有另一个小提琴,可以生成最糟糕但仍然有效的正则表达式,长度为1205306个字符。它看起来类似于这个样子:”。
^(01/01/1800|01/02/1800|01/03/1800|...|12/29/2099|12/30/2099|12/31/2099)$

1

用于检查遵循ISO 8601、SQL标准的有效日期的正则表达式。

  • 范围从1000年到9999年
  • 检查无效日期
  • 检查有效的闰年日期
  • 格式:YYYY-MM-DD HH:MM:SS
^([1-9]\d{3}[\-.](0[13578]|1[02])[\-.](0[1-9]|[12][0-9]|3[01]) ([01]\d|2[0123]):([012345]\d):([012345]\d))|([1-9]\d{3}[\-.](0[469]|11)[\-.](0[1-9]|[12][0-9]|30) ([01]\d|2[0123]):([012345]\d):([012345]\d))|([1-9]\d{3}[\-.](02)[\-.](0[1-9]|1[0-9]|2[0-8]) ([01]\d|2[0123]):([012345]\d):([012345]\d))|(((([1-9]\d)(0[48]|[2468][048]|[13579][26])|(([2468][048]|[13579][26])00)))[\-.](02)[\-.]29 ([01]\d|2[0123]):([012345]\d):([012345]\d))$

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

请尝试上述正则表达式。我尝试了多种组合并发现其有效。
请检查这是否对您也有效。
接受的格式:YYYY-MM-DD
年份接受自1600年。

好奇,您认为这个答案提供的信息有什么其他答案没有提供的? - McKay

-1

你好,找到符合你要求的正则表达式

  • 范围在1800内
  • 现在可以正确检查闰年的日期
  • 日期格式为 DD/MM/YYYY
  • 能够检测无效日期

^(?:(?:31(/)(?:0[13578]|1[02]))\1|(?:(?:29|30)(/)(?:0[13-9]|1[0-2])\2))(?:(?:18|19|20)\d{2})$|^(?:29(/)02\3(?:(?:(?:(?:18|19|20))(?:0[48]|[2468][048]|[13579][26]))))$|^(?:0?[1-9]|1\d|2[0-8])(/)(?:(?:0[1-9])|(?:1[0-2]))\4(?:(?:18|19|20)\d{2})$

enter image description here

图像和调试正则表达式在https://www.debuggex.com/

测试:

  • DD/MM/YYYY
  • 01/12/190 不匹配
  • 29/02/1903 不匹配
  • 37/02/1903 不匹配
  • 09/03/1703 不匹配
  • 09/03/2103 不匹配
  • 09/31/2103 不匹配
  • 29/02/1904 - 匹配
  • 01/12/1988 - 匹配

这个有什么新的东西,现有的问题已经考虑到了吗?此外,它没有进行正确的闰年检查。(这也使用了与OP请求不同的格式) - McKay

-1

仅出于娱乐目的,我添加了我的答案 - 否则我完全同意@Jim。

这将匹配闰年,包括数字少于或多于4位的那些年份。

^\d*((((^|0|[2468])[048])|[13579][26])00$)|((0[48]|(^0*|[2468])[048]|[13579][26]))$

一个Ruby的小测试案例(因为Ruby中,^被替换成了\A$被替换成了\Z):
r = /\A\d*((((\A|0|[2468])[048])|[13579][26])00\Z)|((0[48]|(\A0*|[2468])[048]|[13579][26]))\Z/
100000.times do |year|
  leap = year % 4 == 0 && ((year % 100 != 0) || (year % 400 == 0))
  leap_regex = !year.to_s[r].nil?
  if leap != leap_regex
    print 'Assertion broken:', year, leap, leap_regex, "\n"
  end
end

-1
我试图验证YYYY-MM-DD,其中YYYY可以是两位数,而MM和DD可以是一位数。这就是我想出来的。它将所有世纪视为闰年。
((\d\d)?\d\d-((0?(1|3|5|7|8)|10|12)-(31|30|[21]\d|0?[1-9])|(0?(4|6|9)|11)-(31|30|[21]\d|0?[1-9])|0?2-((2[0-8]|1\d)|0?[1-9]))|(\d\d)?((0|2|4|6|8)(0|4|8)|(1|3|5|7|9)(2|6))-0?2-29)

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