【问题标题】:Regular Expression | Leap Years and More正则表达式 |闰年及更多
【发布时间】:2012-01-28 16:37:42
【问题描述】:

我最近一直在寻找一个正则表达式来做一些客户端日期检查,但我找不到一个可以满足以下条件的表达式:

  • 范围从 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()));    
});

更新:

我应该注意到,这主要是为了看看这样的正则表达式是否可行(因为在这件事上我不选择使用正则表达式)。我知道用于验证日期的其他(和更好的)选项,但是如前所述 - 这是为了看看是否可以通过正则表达式。

【问题讨论】:

  • 为什么要使用正则表达式而不是 Date 类?
  • 2 月 30 日在非闰年有效吗? ;-P
  • 正则表达式并不是很多工作的最佳工具。为此的正则表达式必须考虑年份,并手动实现闰年计算,这将使正则表达式的大小增加三倍?
  • 一个可以验证包括闰年在内的日期的正则表达式?那会让我印象深刻....
  • 是的,这不是一个“有用”的练习。

标签: javascript regex datetime


【解决方案1】:

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

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 个不同的地方进行更改)
  • 需要 2 位数的月份和日期(可以在 ~8 处从表达式中删除严格性)
  • [\/.] 作为分隔符(8 个位置)
  • 尚未经过测试(我们可以对照所有数字组合检查它并与 javascript 日期函数进行比较?[证明我们正在重新发明轮子])

【讨论】:

  • 略有更新。我发现了一个已修复的(仅限性能?)错误。为什么写这样的正则表达式不好。
  • 它也有一个警告,它适用于未来到 2099 年的日期
  • “30 天月”部分中有一个额外的括号,因此例如 11/30/2012 将不匹配,30 天月中的任何第 30 天日期也不匹配。
  • @despot - 1800 和 1900 不是闰年。重新发明轮子不是一个好主意的另一个原因是,您可能不了解实际作用的复杂性。
  • 即使您确实想将 1800 和 1900 视为闰年,闰部分也可以简化为 ((18|19|20)([02468][048]|[13579][26])) 十位和个位的长度大约是十位和个位的两倍在这个例子中,因为我必须把十位的零分开,因为它有特殊的规则,因为 1800 和 1900 不是闰年。
【解决方案2】:

我建议您放弃使用正则表达式的尝试。您最好将日期解析为其组成部分(月、日、年),然后使用数值比较来确保它在正确的范围内。

更好的是,看看 Javascript Date.parse 函数是否能满足您的需求。

使用正则表达式解析日期是可能的,但令人沮丧。很难做到正确,非正则表达式向导很难理解该表达式(这意味着很难证明事情是正确的),并且与其他选项相比,它

【讨论】:

    【解决方案3】:

    我会这样做:

    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)
    

    现场演示: http://jsfiddle.net/9QNRx/

    【讨论】:

      【解决方案4】:

      显然,正则表达式不是执行此操作的理想方式。此外,使用YYYY-MM-DD (ISO 8601) 格式而不是MM/DD/YYYY 更安全。

      也就是说,这是针对 01/01/1800 到 12/31/2099 日期的最短完整正则表达式:

      ^(((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
      

      Here's a fiddle 测试从 00/00/1800 到 99/99/2099 的所有用例。

      另外,为了更有趣,here's another fiddle 会生成仍然有效的最糟糕的正则表达式,长度为 1205306 个字符。它看起来像这样:

      ^(01/01/1800|01/02/1800|01/03/1800|...|12/29/2099|12/30/2099|12/31/2099)$
      

      【讨论】:

        【解决方案5】:

        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 解决当前问题
        • 不会有一个好的答案,因为这甚至不是一个可行的解决方案。
        【解决方案6】:
        ^(((?:(?: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 年开始接受的年份

        【讨论】:

        • 很好奇,你认为这增加了什么其他答案没有增加?
        【解决方案7】:

        这是我用于客户端日期验证的 RegEx。它的范围从 1000 到 2999,验证闰年和可选的时间部分。是不是很漂亮:)

        var r = /^(0[1-9]|1\d|2[0-8]|29(?=-\d\d-(?!1[01345789]00|2[1235679]00)\d\d(?:[02468][048]|[13579][26]))|30(?!-02)|31(?=-0[13578]|-1[02]))-(0[1-9]|1[0-2])-([12]\d{3})(\s([01]\d|2[0-3]):([0-5]\d):([0-5]\d))?$/gm;
        
        r.test('20-02-2013 10:01:07'); // true
        r.test('29-02-1700');          // false
        r.test('29-02-1604 14:01:45'); // true
        r.test('29-02-1900 20:10:50'); // false
        r.test('31-12-2000');          // true
        r.test('31-11-2008 05:05:05'); // false
        r.test('29-02-2004 05:01:23'); // true
        r.test('24-06-2014 24:10:05'); // false
        

        【讨论】:

          【解决方案8】:

          我试图验证 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)
          

          【讨论】:

            【解决方案9】:

            添加我的答案只是为了运动 - 否则我完全同意 @Jim。

            这将匹配闰年,包括数字小于或大于 4 的闰年。

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

            Ruby 中的迷你测试用例(^ 替换为 \A$ 替换为 \Z,因为 Ruby):

            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
            

            【讨论】:

              【解决方案10】:

              使用时刻(不是正则表达式)我做了以下事情:

              假设您有一个 ISO 日期作为字符串值:

              var isoDate = '2016-11-10';
              var parsedIsoDate = moment(isoDate, ['YYYY-MM-DD'], true).format('YYYY-MM-DD');
              
              if (parsedIsoDate !== isoDate) {
                  // Invalid date.
              }
              

              【讨论】:

                【解决方案11】:

                您好,根据您的要求查找 RegEx

                • 范围从 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})$

                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 请求不同的格式)
                【解决方案12】:

                ((0[13578]|1[02])[/.]31/.[0-9]{2})|((01|0[3-9]|1[1-2]) /./.[0-9]{2})|((0[1-9]|1[0-2])/./.[0-9]{2})|((02)[/ .]29/.)

                短版答案不适用于任何一年的 10/29 和 10/30

                import java.util.Date;
                import java.util.regex.Matcher;
                import java.util.regex.Pattern;
                
                import org.joda.time.LocalDate;
                import org.joda.time.format.DateTimeFormat;
                import org.joda.time.format.DateTimeFormatter;
                
                public class RegxDateTest {
                
                public static void main(String[] args) {
                    // String to be scanned to find the pattern.
                      String line = "This order was placed for QT3000! OK?";
                        String pattern ="((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))";
                      // Create a Pattern object
                      Pattern r = Pattern.compile(pattern);
                      LocalDate startDate = new LocalDate("1950-01-01");
                      LocalDate endDate = new LocalDate("2020-01-01");
                      for (LocalDate date = startDate; date.isBefore(endDate); date = date.plusDays(1))
                      {
                          if (date.toString("MM/dd/yyyy").matches(pattern)) {
                             // System.out.println("This date does  match:  " + date.toString("MM/dd/yyyy") );
                            }else{
                                  System.out.println("This date does not match:  " + date.toString("MM/dd/yyyy") );
                            }
                
                      }
                      String baddate1="02/29/2016";
                      if (baddate1.matches(pattern)) {
                          System.out.println("This date does  match:  " + baddate1 );
                      }else{
                          System.out.println("This date does not match:  " + baddate1 );
                      }
                      System.out.println("alldone:  "  );
                
                }
                

                }

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-05-16
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2019-11-19
                  • 1970-01-01
                  相关资源
                  最近更新 更多