正则表达式|闰年等等

时间:2011-12-27 18:22:25

标签: javascript regex datetime

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

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

更新

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

12 个答案:

答案 0 :(得分:25)

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

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 - 8日始终有效

(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日期函数进行比较?[证明我们正在重新发明轮子])

答案 1 :(得分:13)

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

更好的是,看看Javascript Date.parse函数是否能达到你想要的效果。

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

答案 2 :(得分: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)

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

答案 3 :(得分:2)

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

那就是说,这是1800年1月1日至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

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)$

答案 4 :(得分:1)

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

答案 5 :(得分:0)

使用时刻(不是正则表达式)我已经完成了以下操作:

假设您将ISO日期作为字符串值:

var isoDate = '2016-11-10';
var parsedIsoDate = moment(isoDate, ['YYYY-MM-DD'], true).format('YYYY-MM-DD');

if (parsedIsoDate !== isoDate) {
    // Invalid date.
}

答案 6 :(得分: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]))))$

请尝试以上Reg表达式。我尝试了多种组合,发现它们正在发挥作用。

请检查这是否适合您。

接受格式:YYYY-MM-DD

从1600年开始接受

答案 7 :(得分:0)

根据您的要求找到正则表达式

  • 范围从1800
  • 现在使用proper年执行正确的日期检查
  • 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/

处镜像和调试RegEx

测试:

  • DD / MM / YYYY
  • 01/12/190 不匹配
  • 29/02/1903 不匹配
  • 37/02/1903 不匹配
  • 09/03/1703 不匹配
  • 09/03/2103 不匹配
  • 09/31/2103 不匹配
  • 1904年2月29日-比赛
  • 1988年1月12日-比赛

答案 8 :(得分:-1)

这是我用于客户端日期验证的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

答案 9 :(得分:-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)

答案 10 :(得分:-1)

为运动添加我的答案 - 否则我完全赞同@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

答案 11 :(得分:-2)

  

((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任何一年长版本在下面工作是一个简单的java脚本程序我写的测试

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:  "  );

}

}