I've recently been looking for a regular expression to do some client side date checking, and I haven't been able to find one that can satisfy the following criteria:
(These constraints were outside of my scope and are a requirement as per the client, despite my efforts to convince them this wasn't the best route)
Current code:
$('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()));
});
Update:
I should note that this is primarily to see if a regular expression like this would be possible (as the use of a Regex is not my choice in this matter). I am aware of the other (and better) options for validating a date, however as previously mentioned - this is to see if it was possible through a regular expression.
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:
[\/.]
as seperators (8 places)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.
Obviously regular expressions are not the ideal way to do this. Also, it's much safer to be working with YYYY-MM-DD
(ISO 8601) format, not MM/DD/YYYY
.
That said, here's going for the shortest fully-working regular expression for dates from 01/01/1800 to 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))$
Length: 162 characters.
Breakdown:
^ # 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 that tests all use cases from 00/00/1800 to 99/99/2099.
Also, for more fun, here's another fiddle that generates the lousiest possible regular expression that still works, 1205306 characters long. It looks something like this:
^(01/01/1800|01/02/1800|01/03/1800|...|12/29/2099|12/30/2099|12/31/2099)$
This is the RegEx I use for date validation on client-side. It has a range from 1000 to 2999, validates leap years and optionally the time part. Isn't it gorgeous :)
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
I was trying to validate YYYY-MM-DD, where YYYY can be two digit and MM and DD can be one. This is what I came up with. It treats all centuries as leap years.
((\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)
Adding my answer just for sport - otherwise I fully agree with @Jim.
This will match leap years, including the ones with digits fewer or more than 4.
^\d*((((^|0|[2468])[048])|[13579][26])00$)|((0[48]|(^0*|[2468])[048]|[13579][26]))$
A mini test case in Ruby (^
replaced with \A
and $
with \Z
, because 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
Using moment (not regex) I've done the following:
Assuming you have an ISO date as a string value:
var isoDate = '2016-11-10';
var parsedIsoDate = moment(isoDate, ['YYYY-MM-DD'], true).format('YYYY-MM-DD');
if (parsedIsoDate !== isoDate) {
// Invalid date.
}
Hello Find RegEx for your Requirement
^(?:(?: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})$
Image and debug RegEx At https://www.debuggex.com/
Testing:
((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/.)
The short version answer does not work for 10/29 and 10/30 any year the long version does work below is a simple java script program I wrote to test
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: " );
}
}
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/
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
^(((?:(?: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]))))$
Please try the above Reg Expression. I tried multiple combinations and found to be working.
Please check if this works for you too.
Format Accepted : YYYY-MM-DD
Year accepted from 1600
©2020 All rights reserved.