Regular Expression | Leap Years and More - javascript

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:
Has a range from 1800 - Now
Performs proper date checking with leap years
MM/DD/YYYY Form
Invalid Date Checking
(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:
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])

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

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

RegEx to check for valid dates following ISO 8601, SQL standard.
Has a range from 1000-9999
Checks for Invalid Dates
Checks for valid leap year dates
Format: 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))$

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

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
Has a range from 1800
Now Performs proper date checking with leap years
DD/MM/YYYY Format
Invalid Date Checking
^(?:(?: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:
DD/MM/YYYY
01/12/190 Not Match
29/02/1903 Not Match
37/02/1903 Not Match
09/03/1703 Not Match
09/03/2103 Not Match
09/31/2103 Not Match
29/02/1904 - Match
01/12/1988 - Match

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

Related

Why does the Chrome V8 JavaScript engine recognize "TG-1" through "TG-12" as valid dates/times?

I implemented a CSV parser that guesses the type formats for each column but I have found that the JavaScript Date class believes "TG-1" is a valid date/time.
Is this some obscure date format I haven't seen before that Chrome supports? I wouldn't think this is a valid date and looking at different date ISO standards I haven't seen a reference to this.
Chrome 74 says it's valid.
Firefox 64 says it's not valid.
let validDate = true;
try{
d = new Date("TG-1");
d.toISOString()
}catch(e){
validDate = false
}
console.log(validDate);
Any string followed by - and a number 1-12 is considered valid:
d = new Date("adsfadgag-12")//valid per V8
To quote from the V8 sourcecode:
Legacy dates:
Any unrecognized word before the first number is ignored.
Parenthesized text is ignored.
An unsigned number followed by ':' is a time value, and is added to the TimeComposer. A number followed by '::' adds a second zero as well. A number followed by '.' is also a time and must be followed by milliseconds.
Any other number is a date component and is added to DayComposer. A month name (or really: any word having the same first three letters as a month name) is recorded as a named month in the Day composer. A word recognizable as a time-zone is recorded as such, as is (+|-)(hhmm|hh:).
Legacy dates don't allow extra signs ('+' or '-') or umatched ')' after a number has been read (before the first number, any garbage is allowed).
Intersection of the two: A string that matches both formats (e.g. 1970-01-01) will be parsed as an ES5 date-time string - which means it will default to UTC time-zone. That's unavoidable if following the ES5 specification.
After a valid "T" has been read while scanning an ES5 datetime string, the input can no longer be a valid legacy date, since the "T" is a garbage string after a number has been read.
In other words: This behaviour is not really planned, it's just that some browsers behaved like this somewhen, and therefore this weird behaviour has to be kept. Date(...) will try to parse nearly anything without complaining.

Validate dateFormat in dd/mm/yyyy using regex [duplicate]

This question already has answers here:
Javascript date regex DD/MM/YYYY
(13 answers)
Closed 7 years ago.
I want to validate the format of the date value entered by a user using regex with javascript.
My regex doesn't allow the '/' character , /[^0-9\.]/g,''
But I want to let '/' pass the regex test too. What modification do I need to make here?
Modified from this answer you can be pretty exact with this. This works for the years 1000-9999, is Proleptic Gregorian and assumes that we won't change how leap-years work until the year 9999 ;)
^(?:(?:(?:0[1-9]|1\d|2[0-8])/(?:0[1-9]|1[0-2])|(?:29|30)/(?:0[13-9]|1[0-2])|31/(?:0[13578]|1[02]))/[1-9]\d{3}|29/02/(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00))$
Debuggex Demo
"20/11/1992".match(/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}$/)
The above snippet should do, but there are too many validations to be performed on dates, so I wouldn't recommend regex.
Instead, I'd say do it like most websites do and place 3 combo boxes (dd/mm/yyyy), and allow the user to select a date, then you validate that date using the Date() constructor (if the values haven't changed, the date is correct).
note: the answer is based upon the assumption that you don't want to use any of the existing libraries (or the native validation provided by browser when using input[type="date"])
You can use this regex:
/(^(((0[1-9]|[12][0-8])[\/](0[1-9]|1[012]))|((29|30|31)[\/](0[13578]|1[02]))|((29|30)[\/](0[4,6,9]|11)))[\/](19|[2-9][0-9])\d\d$)|(^29[\/]02[\/](19|[2-9][0-9])(00|04|08|12|16|20|24|28|32|36|40|44|48|52|56|60|64|68|72|76|80|84|88|92|96)$)/
This validates the date with format dd/mm/yyyy and also checks for leap years.
It depends on how strict you need to be? I thing that simple:
/[0-3]\d\W[01]\d\W(?>19|20)\d{2}/g
should be sufficient.
day: [0-3]\d 2 digits, first 0-3, second any number \d
month: [01]\d 2 digits, first 0 or 1, second any number
year: (?>19|20)\d{2} 4 digits, starts with 19 or 20 (for 19th and 20th century) and next any digit two times {2}
Also note, I used \W to match single non-word character as workaround to match /. Are you sure that you cannot use escaped slash \/ instead?

Create numerical restrictions for date; year, month and day

I'm trying to put restrictions on a date input field so users don't put in exaggerating dates like the year 3000. I found this neat solution which works. But I want the date in yyyy/mm/dd format with year also having restriction, so mm is between 1-12, dd is between 1-31, and yy is between 1900-2100.
Here's the jsfiddle, I can't get it to work with format yyyy/mm/dd. If I change the dtArrays to
dtMonth = dtArray[5];
dtDay= dtArray[7];
dtYear = dtArray[1];
Year works but mm ends up being in place of dd. What could I be doing wrong? Also are there any better ways of accomplish this? Last question.. this problem seems to be pretty simple, what books on jquery/javascript would you recommend so I may be able to get this on my own?
I think you've misunderstood what you get in dtArray (which isn't a great name). It's the output of the capture groups from this regex:
/^(\d{1,2})(\/|-)(\d{1,2})(\/|-)(\d{4})$/
which matches mm/dd/yyyy (where those are digits) so
[1] = m or mm
[2] = a / or - separator
[3] = d or dd
[4] = a / or - separator
[5] = yyyy
They aren't offsets into the string. (dtArray[0] will be the whole date matched.) With your modified regex is
/^(\d{4})(\/|-)(\d{1,2})(\/|-)(\d{1,2})$/
i.e. four digits in the first position, then you'll get
[1] = yyyy
[2] = a / or - separator
[3] = m or mm
[4] = a / or - separator
[5] = d or dd
and so
dtYear = dtArray[1];
dtMonth = dtArray[3];
dtDay = dtArray[5];
(Note at this point that the three variables will actually be strings, not integers, albeit containing string representations of integer values. However JavaScript being what it is, they'll be coerced into integers when you try and use them as integers.)

Why doesn't this regular expression work the way I want it?

I'm trying to make the regular expression find the numbers in a date string (eg, 04/05/1989). This is what I have:
\A0[1-9]{2}\/0[1-9]{2}\/[1900-2012]\z
The square brackets create a character range, not a number range:
[A-Z] matches all ASCII characters from A to Z.
[1900-2012] matches the 1, the 9, the 0, the range of ASCII characters between 0 and 2, the 0, the 1 and the 2. Effectively the same can be expressed as [0-29].
Within a character range expression, the order of characters is not important. [0815] is the same as [8510]. Hence [1900] is the same as [019].
You want (19[0-9]{2}|200[0-9]|201[0-2]) to match the correct year range. Or, if you just want to match four digits and don't want range validation you can use ([0-9]{4})
Same for month and day. You can do it with range validation or you can do it by matching one or two digits. I'd recommend the latter.
^([0-9]{1,2})/([0-9]{2})/([0-9]{4})$
Try to convert the result to a date value. If that fails, the input was invalid. If it succeeds, check if the resulting date falls into your required range. Regex is the wrong tool to provide date validity checks.
I had answered a similar question over here: How to parse a date in format "YYYYmmdd" in JavaScript?. It just needs some modifications in the extraction part.
EDIT:
You could modify that function this way:
function parse(str) { // str format: 'dd/mm/yyyy'
var d = str.substr(0,2),
m = parseInt(str.substr(3,2)) - 1,
y = str.substr(6,4);
var D = new Date(y,m,d);
return (D.getFullYear() == y && D.getMonth() == m && D.getDate() == d) ? D : 'invalid date';
}
(?<!\d)((0?\d)|([12]\d)|(3[01]))/((0?\d)|(1[0-2]))/((19)|(20))\d\d
Seems to work in the tests I did.
(?<!\d)((((0?\d)|([12]\d)|(3[01]))/(((0?((1[02]?)|([3578]))))|(1[0-2])))|(((0?\d)|([12]\d))/(0?2))|(((0?\d)|([12]\d)|(30))/((0?[2469])|(11))))/((19)|(20))\d\d
The previous regex matches invalid dates like 31/02/2012 but this ones seems a little more normative.
IMHO this won't match valid 10/10/1989 but will match invalid 099/0991989. First of all 0[1-9]{2} matches 0 plus two digits between 1 and 9. Also [1900-2012] is not what you want.
try this
\A[0-9]{2}\/[0-9]{2}\/[1900-2012]\z
it should match dates in format dd/mm/yyyy(1900 to 2012)

What regular expression should I use to match this date pattern: DAY.month.YEAR?

I have this date format:
DAY.month.YEAR (today: 28.06.2011)
I will need a Regular Expression (RegEx) pattern for matching this date format.
Can anyone post a solution for this problem?
Derived from http://www.regular-expressions.info/dates.html:
(0[1-9]|[12][0-9]|3[01])\.(0[1-9]|1[012])\.(19|20)\d\d
This matches a date in dd.mm.yyyy format from between 01.01.1900 and 31.12.2099. It will, however, still match invalid dates, because validating leap years, for example, can not be done with regex (at least not very easily).
However, a regex is probably unnecessary. Javascript example:
var date = "28.06.2011".split("."); // split up the date by the dots
// parse the components into integers
var day = parseInt(date[0]);
var month = parseInt(date[1]);
var year = parseInt(date[2]);
// if you want the date in a date object, which will fix leap years (e.g. 31.02 becomes 03.03
var date = new Date(year, month - 1, day);
Note that when creating a date object, month starts at zero.
Which method you use depends on what you need this for. If you want to find dates in a text, use the regex. If you simply want to parse the date into a date object, use the second method. Some extra validation is possibly necessary to make sure the date is valid, as the javascript Date object does not care about February having 31 days, it simply wraps over to 3. of March.
If you wish to match the format of the date, than it will be enough to use:
\d\d\.\d\d\.\d\d\d\d
However, as David Hall pointed out in his comment to your question, you will still need to validate the date in your code. Doing this in a regex isn't easy, as you can see from Harpyon answer which - still making a "preliminary check that filter out many wrong possiblities" - also accepts 31.02.2011 as a valid date and misses out on the French revolution (14 July 1789).

Categories