
De regex hieronder valideert een Franse datum DD/MM/JJJJ (scheidingsteken /, - of .) en weigert onmogelijke datums zoals 30 februari of 31 april, schrikkeljaren inbegrepen. Voor een ISO-formaat JJJJ-MM-DD volstaat een korte regex als je daarna bevestigt met het object Date in JavaScript of checkdate() in PHP.
Een met de hand ingetypte datum valideren is lastiger dan het lijkt: “30/02/2026” heeft de juiste vorm en bestaat niet. De datum regex hieronder controleert het formaat DD/MM/JJJJ, met /, - of . als scheidingsteken, en weigert onmogelijke dagen, inclusief 29 februari in niet-schrikkeljaren. Daarna vind je de versies voor JavaScript, PHP en HTML, de ISO-variant en het geval waarin het object Date het beter doet dan een regex.
De kant-en-klare datum regex
^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$Ze is lang omdat ze redeneert als een kalender. Drie takken, gescheiden door |:
- De 31 bestaat alleen in maanden met 31 dagen (
01, 03, 05, 07, 08, 10, 12), en de 29 en de 30 in alle maanden behalve februari. - 29 februari wordt alleen geaccepteerd als het jaar een schrikkeljaar is: deelbaar door 4 (
0[48]|[2468][048]|[13579][26]), of een eeuwjaar deelbaar door 400 ((?:16|[2468][048]|[3579][26])00). 2000 en 2024 komen erdoor, 1900 en 2023 worden geweigerd. - Van 1 tot en met 28 accepteren alle maanden de datum.
De backreferences \1, \2, \3 en \4 eisen dat het tweede scheidingsteken hetzelfde is als het eerste: 07/09/2026 en 07-09-2026 komen erdoor, 07/09-2026 niet.
Om alleen de schuine streep te accepteren, vervang je elke (\/|-|\.) door (\/). De haakjes blijven nodig: de backreferences \1 tot \4 wijzen naar deze groepen.
De geteste datums
Echt resultaat van de regex onder Node.js 22 en PHP 8.5, identiek in beide engines:
| Invoer | Resultaat | Reden |
|---|---|---|
31/12/2026 | geaccepteerd | 31 in een maand met 31 dagen |
29/02/2024 | geaccepteerd | 2024 is een schrikkeljaar |
29/02/2000 | geaccepteerd | 2000 is deelbaar door 400 |
01/01/2026 en 1/1/2026 | geaccepteerd | de voorloopnul is optioneel |
07-09-2026 en 07.09.2026 | geaccepteerd | andere scheidingstekens toegestaan |
29/02/2023 | geweigerd | 2023 is geen schrikkeljaar |
29/02/1900 | geweigerd | eeuwjaar niet deelbaar door 400 |
30/02/2026 | geweigerd | februari heeft nooit 30 dagen |
31/04/2026 | geweigerd | april heeft 30 dagen |
00/01/2026 | geweigerd | dag 0 bestaat niet |
12/13/2026 | geweigerd | maand 13: de regex is inderdaad DD/MM en niet MM/DD |
07/09-2026 | geweigerd | verschillende scheidingstekens |
2026-09-07 | geweigerd | ISO-formaat, zie verderop |
07/09/26 en 31/12/1899 | geaccepteerd | jaar met twee cijfers of vóór 1900: begrens dit indien nodig in je eigen code |
JavaScript-versie
De functie verankert de regex met ^ en $, verwijdert de spaties rond de invoer en geeft een boolean terug.
/**
* Controleert een datum in het formaat DD/MM/JJJJ, DD-MM-JJJJ of DD.MM.JJJJ.
* @param {string} date
* @returns {boolean}
*/
function validateDate(date) {
const reg = /^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$/;
return reg.test(String(date).trim());
}
console.log(validateDate('29/02/2024')); // true
console.log(validateDate('29/02/2023')); // false
console.log(validateDate('31/04/2026')); // falseIn een formulier is het patroon hetzelfde als bij het valideren van een e-mailadres: luisteren naar submit, preventDefault() aanroepen als de datum ongeldig is, het bericht naast het veld tonen.
PHP-versie
Controle aan de kant van de server is verplicht: JavaScript kan worden uitgeschakeld. Hetzelfde patroon werkt met preg_match(), en de backreferences schrijf je als \1 in een PHP-string tussen aanhalingstekens.
/**
* Controleert een datum in het formaat DD/MM/JJJJ, DD-MM-JJJJ of DD.MM.JJJJ.
*/
function isValidDate(string $date): bool
{
$pattern = '/^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$/';
return preg_match($pattern, trim($date)) === 1;
}
var_dump(isValidDate('29/02/2024')); // bool(true)
var_dump(isValidDate('30/02/2026')); // bool(false)Het alternatief zonder regex: checkdate()
PHP kan al zelf zeggen of een combinatie dag, maand, jaar bestaat. Een kleine regex om op te splitsen, dan checkdate() om te oordelen: dat is korter en leesbaarder.
function isValidDateFr(string $date): bool
{
if (!preg_match('#^(\d{1,2})[/.-](\d{1,2})[/.-](\d{4})$#', trim($date), $m)) {
return false;
}
return checkdate((int) $m[2], (int) $m[1], (int) $m[3]); // maand, dag, jaar
}
var_dump(isValidDateFr('29/02/2024')); // bool(true)
var_dump(isValidDateFr('29/02/2023')); // bool(false)
var_dump(isValidDateFr('31/04/2026')); // bool(false)Let op DateTime::createFromFormat('d/m/Y', '29/02/2023'): dit geeft niet false terug maar 1e maart 2023, en meldt de afwijking in DateTime::getLastErrors(). Voor een strikte controle is checkdate() veiliger.
HTML-versie
Typt de gebruiker de datum in vrije tekst, dan past het attribuut pattern de regex toe vóór het versturen. Het is impliciet verankerd: je laat ^ en $ weg.
<label for="naissance">Date de naissance (JJ/MM/AAAA)</label>
<input
id="naissance"
name="naissance"
type="text"
inputmode="numeric"
placeholder="JJ/MM/AAAA"
pattern="(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})|(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))|(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})"
title="Une date valide au format JJ/MM/AAAA"
required
>Maar de beste HTML-oplossing is geen regex: het is type="date". De browser toont een kiezer die is afgestemd op de taal van de gebruiker, verhindert 30 februari, en geeft de waarde altijd door in het ISO-formaat JJJJ-MM-DD, ongeacht de weergave.
<input id="naissance" name="naissance" type="date" min="1900-01-01" max="2026-12-31" required>Gevolg aan de kant van de server: met type="date" moet je het ISO-formaat valideren, niet het Franse formaat.
ISO-variant JJJJ-MM-DD
Voor een ISO-datum controleert een korte regex de vorm, en bevestigt het object Date dat de dag bestaat. Samen vervangen ze de grote regex.
function isValidIsoDate(value) {
const m = /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/.exec(value);
if (!m) return false;
const [, y, mo, d] = m.map(Number);
const date = new Date(y, mo - 1, d);
// new Date(2026, 1, 30) geeft 2 maart terug: we controleren dat er niets is verschoven
return date.getFullYear() === y && date.getMonth() === mo - 1 && date.getDate() === d;
}
console.log(isValidIsoDate('2026-09-07')); // true
console.log(isValidIsoDate('2026-02-30')); // false
console.log(isValidIsoDate('2026-13-01')); // falseDezelfde techniek geldt voor het Franse formaat: opsplitsen met /^(\d{1,2})[\/.-](\d{1,2})[\/.-](\d{4})$/, en dan controleren met Date. Dat is het JavaScript-equivalent van checkdate() in PHP.
En het Amerikaanse formaat MM/DD/JJJJ?
De regex uit dit artikel is Frans: eerst de dag, dan de maand. Voor een Amerikaans publiek verwissel je de eerste twee blokken, of, eenvoudiger, normaliseer je alles naar ISO al bij de invoer met type="date". Een toepassing die beide volgordes accepteert zonder onderscheid, zit er in de helft van de gevallen naast voor alle datums van 1 tot en met 12.
Twee datums vergelijken na validatie
Zodra de datum gevalideerd en omgezet is naar een Date-object, zijn vergelijkingen direct: de operatoren < en > vergelijken de tijdstippen, en de aftrekking geeft een verschil in milliseconden.
const debut = new Date('2026-09-07');
const fin = new Date('2026-12-25');
console.log(debut < fin); // true
console.log(Math.round((fin - debut) / 86_400_000)); // 109 dagenOpmaak voor weergave (toLocaleDateString, Intl.DateTimeFormat) is een ander onderwerp, zoals datum en tijd in PHP of Carbon in Laravel.
Regex of Date-object: wat kies je?
- De grote regex als je één regel wilt die identiek is in JavaScript, PHP en HTML, zonder afhankelijk te zijn van een library of een
Date-object. - Opsplitsen +
Dateofcheckdate()als leesbaarheid telt, of als je de datum daarna moet bewerken: je hebt hem al als object. type="date"voor elk modern formulier: de invoer wordt begeleid, de waarde genormaliseerd, en alleen de ISO-controle aan de kant van de server blijft over.
De andere validaties uit de reeks: e-mailadres, IP-adres, postcode en wachtwoord.
Veelgemaakte fouten
new Date(2026, 1, 30) geeft geen foutmelding: het levert 2 maart 2026 op. Vergelijk altijd jaar, maand en dag na constructie.2026-09-07 wordt geweigerd door de regex DD/MM/JJJJ. Gebruik de ISO-variant uit dit artikel, of beter nog, input type="date", die dit formaat al teruggeeft.type="date" toont “07/09/2026” in het Nederlands, maar de doorgegeven waarde is “2026-09-07”. De validatie aan de kant van de server moet het ISO-formaat verwachten.

