Honestly, this regex that you are using, in my opinion, is nothing practical and I would avoid using in any system in production. Just see the difficulty you’re having to understand and modify it. Think about whether it’s really worth it.
This regex makes validations that are much easier to do outside of it, such as checking if a year is leap (and knowing whether February should be 28 or 29 days), and checking months with 30 or 31 days. But why all this, if it is much simpler to check out it? Regex processes text, even digits like 1
or 2
are treated as mere characters, and any mathematical calculation is much easier to do outside the regex.
That being said, if you receive a string and want to check if it matches a date in the 'dd/mm/yyyy' format, you do not need to use a super-regex-complicated-all-rounder.
You can use a simpler regex, only to validate the format "two digits, bar, two digits, bar, four digits" and then validate these values separately:
let match = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec('20/10/2018');
if (match) {
let dia = parseInt(match[1]);
let mes = parseInt(match[2]);
let ano = parseInt(match[3]);
let d = new Date(ano, mes - 1, dia);
let dataValida = d.getFullYear() == ano
&& d.getMonth() + 1 == mes
&& d.getDate() == dia;
console.log(dataValida); // true
} else {
console.log('string não está no formato dd/mm/aaaa, ou não é uma data');
}
The regex only serves to check if the string has exactly two digits, bar, plus two digits, bar, plus 4 digits. The markers ^
and $
means, respectively, the beginning and the end of the string, thus ensuring that the string only has what is in regex.
Using \d{2}
and \d{4}
I guarantee the correct amounts of digits, and in case the regex returns a match, I can turn the respective values into numbers, using parseInt
. This is possible because each number is in parentheses in the regex, and this forms a catch group (with this I can get each group using match[numero_do_grupo]
).
The builder of Date
receives the year, month and day, with the care of subtracting 1 of the month, since for this class January is zero, February is 1, etc.
The problem is that this class accepts invalid values, such as month 13, day 32, etc., and makes some adjustments to the final result (for example, if you try to create January 32, the result is February 1). Therefore, to know if the values of the day, month and year are valid, just compare them with the Date
valet. If there was no adjustment it is because the values are valid, otherwise it is an indicative that some value is invalid (it can be day 31 in months that only has 30 days, or February 29 in a non-leap year, etc).
If you want to use other tabs besides the bar (such as -
or .
), the regex changes a little:
let match = /^(\d{2})([-\/.]?)(\d{2})\2(\d{4})$/.exec('20.10.2018');
if (match) {
let dia = parseInt(match[1]);
let mes = parseInt(match[3]);
let ano = parseInt(match[4]);
let d = new Date(ano, mes - 1, dia);
let dataValida = d.getFullYear() == ano
&& d.getMonth() + 1 == mes
&& d.getDate() == dia;
console.log(dataValida); // true
} else {
console.log('string não está no formato dd/mm/aaaa, ou não é uma data');
}
Now I put another capture group to the tab: ([-\/.]?)
. The excerpt [-\/.]
means "a hyphen, or a bar, or a point", and the ?
makes this section optional.
Then I use the reference \2
, which means "the same string that was captured in group 2". Group 2 is the second pair of parentheses, which in this case is the separator. This ensures that the same separator that was used between day and month will be between month and year. And as it is optional, if there is no separator, the \2
will be the empty string, ensuring that either it has the same separator, or it has no.
Another point of attention is that as I have included another capture group (for the tab), the month and year are now groups 3 and 4 (match[3]
and match[4]
).
Moment js.
Another alternative is to use the library Moment js.. In case, you can pass an array of possible formats and then use isValid()
to determine whether the date is valid.
let formatos = ['DD/MM/YYYY', 'DD-MM-YYYY', 'DD.MM.YYYY', 'DDMMYYYY'];
console.log(moment('29/02/2016', formatos).isValid()); // true
console.log(moment('30-04-2017', formatos).isValid()); // true
console.log(moment('31.12.2018', formatos).isValid()); // true
console.log(moment('10032019', formatos).isValid()); //true
// datas inválidas
console.log(moment('29/02/2017', formatos).isValid()); // false
console.log(moment('31-04-2017', formatos).isValid()); // false
<script src="https://momentjs.com/downloads/moment.min.js"></script>
If you want even proceed with the giant regex, you can withdraw the ?
after zeros, so they are no longer optional (with the optional zero to regex accepts 04
and 4
).
I also traded the separators for [-\/.]?
to make them optional. Your regex uses \/|-|\.
(a bar or hyphenate or point), but when we have several options with only one character each, it is easier to use the square brackets. Putting the ?
then make the snippet optional, and from what I saw regex uses the same thing I did in the second example above, using \1
, \2
, etc so that the separator is the same.
Finally, I removed the ?
after the first two digits of the year, so regex will require years with only 4 digits:
^(?:(?:31([-\/.]?)(?:0[13578]|1[02]))\1|(?:(?:29|30)([-\/.]?)(?:0[1,3-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)\d{2})$|^(?:29([-\/.]?)02\3(?:(?:(?:1[6-9]|[2-9]\d)(?:0[48]|[2468][048]|[13579][26]))))$|^(?:0[1-9]|1\d|2[0-8])([-\/.]?)(?:(?:0[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)\d{2})$
See here some examples of it working. Below are some tests as well:
let r = /^(?:(?:31([-\/.]?)(?:0[13578]|1[02]))\1|(?:(?:29|30)([-\/.]?)(?:0[1,3-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)\d{2})$|^(?:29([-\/.]?)02\3(?:(?:(?:1[6-9]|[2-9]\d)(?:0[48]|[2468][048]|[13579][26]))))$|^(?:0[1-9]|1\d|2[0-8])([-\/.]?)(?:(?:0[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)\d{2})$/;
console.log(r.test('29/02/2016')); // true
console.log(r.test('30-04-2019')); // true
console.log(r.test('20.12.2018')); // true
console.log(r.test('15032019')); // true
console.log(r.test('10-10/2018')); // false
console.log(r.test('29/02/2017')); // false
console.log(r.test('31/04/2018')); // false
console.log(r.test('32/01/2019')); // false
Without a doubt, the @hkotsubo kills letter when it comes to regex.
– RXSD
without doubt incredible explanation, and its function is really great, the problem is that I really need Regex for not being able to use javascript, I can only use regex even
– flourigh
@flourigh I updated the answer with an alternative, see if that’s what you need
– hkotsubo
@flourigh if you can’t use JS, you could ask the [tag:javascript]
– Sam
is that the site uses javascript, I can not send javascript, I can put a regex in the indicated location that is all done in javascript, or vuejs
– flourigh
@Andréfilipe the guy is too genius, besides explaining in detail, made the thing work perfectly, did not solve the problem, taught to solve by showing the errors, man, I understood more in this answer than in several manuals
– flourigh
@flourigh Most "manuals" of regex are really bad, because they only explain the syntax, but not how to use it. But it has good sites too, I like it a lot that and that. And books, I recommend that and that :-)
– hkotsubo
@hkotsubo Thank you very much for helping also with the references for study, will help me very much because I need to do several validations with regex, now I’m going to go to the validation of CPF, I have qm javascript but also need to be in regex, I hope one day I can help as you helped me
– flourigh