Regular Expression | Leap Years and More

I would suggest that you abandon the attempt to use regular expressions for this. You're much better off parsing the date into its constituent parts (month, day, year), and then using numerical comparisons to make sure it's in the proper range.

Better yet, see if the Javascript Date.parse function will do what you want.

Parsing dates with regular expressions is possible, but frustrating. It's hard to get right, the expression is difficult for non-regex wizards to understand (which means it's difficult to prove that the thing is correct), and it is slow compared to other options.


this regular expression for YYYY-MM-DD format

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

This is how I would do it:

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];
}

Usage:

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/


As is mentioned elsewhere, regular expressions almost certanily not what you want. But, having said that, if you really want a regular expression, here is how it is built:

31 day months

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

30 day months

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

February 1-28 always valid

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

February 29 also valid on leap years

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

which means it would be this if you put it all together:

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

This version is a little shorter, but a little harder to understand.

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

These scripts are long and unmaintainable. It should be clear that this isn't a good idea, but it is possible.

Caveats:

  • range 1800-2099 (more can be added without too much difficulty, but requires changes in 4-6 disparate places)
  • requires 2 digit months and days (the strictness could be removed from the expression in ~8 places)
  • [\/.] as seperators (8 places)
  • Hasn't been tested (we could check it against all digit combinations and compare with the javascript date function? [proof that we're reinventing the wheel])