Why Date Validation Regex is Surprisingly Complex
Date strings look simple but are deceptive. The challenge is not just matching digits — it is constraining the ranges: month must be 01–12, day must be 01–31. And even a perfectly ranged date like 2026-02-30 or 2026-04-31 is impossible on the calendar. Regex handles format validation; calendar validation requires real date parsing logic.
A second challenge is the diversity of conventions. The same date can be written as 2026-08-05 (ISO), 05/08/2026 (UK), 08/05/2026 (US), or 05.08.2026 (European), all of which mean August 5th 2026 — but only to someone who knows which convention is in use. Misread a US date as UK and you get month-day transposition bugs.
Pattern Deep Dive — ISO 8601 (YYYY-MM-DD)
/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/
// Breakdown:
// ^ — start of string
// \d{4} — exactly four digits for the year (0000–9999)
// - — literal hyphen separator
// (0[1-9]|1[0-2]) — month: 01–12
// 0[1-9] — 01, 02, 03, 04, 05, 06, 07, 08, 09
// 1[0-2] — 10, 11, 12
// - — literal hyphen separator
// (0[1-9]|[12]\d|3[01]) — day: 01–31
// 0[1-9] — 01–09
// [12]\d — 10–29
// 3[01] — 30, 31
// $ — end of string
// Test cases:
'2026-08-05'.match(pattern); // matches (valid ISO date)
'2026-13-01'.match(pattern); // no match (month 13 is invalid)
'2026-00-15'.match(pattern); // no match (month 00 is invalid)
'2026-02-30'.match(pattern); // matches! (regex can't detect Feb 30)
'2026-8-5'.match(pattern); // no match (no leading zeros → use \d?[1-9])Language Examples
JavaScript — ISO Date Validation with Calendar Check
function validateISODate(input) {
// Step 1: format check
const formatOk = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/.test(input);
if (!formatOk) return false;
// Step 2: calendar check — Date parsing handles Feb 30, Apr 31 etc.
const d = new Date(input);
return !isNaN(d.getTime()) && d.toISOString().startsWith(input);
}
validateISODate('2026-08-05'); // true
validateISODate('2026-02-30'); // false (calendar check fails)
validateISODate('2026-13-01'); // false (format check fails)Python — Multiple Formats
import re
from datetime import datetime
PATTERNS = {
'iso': re.compile(r'^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$'),
'us': re.compile(r'^(0[1-9]|1[0-2])/(0[1-9]|[12]\d|3[01])/\d{4}$'),
'eu': re.compile(r'^(0[1-9]|[12]\d|3[01])/(0[1-9]|1[0-2])/\d{4}$'),
}
FORMAT_STRINGS = {'iso': '%Y-%m-%d', 'us': '%m/%d/%Y', 'eu': '%d/%m/%Y'}
def validate_date(value: str) -> bool:
for name, pattern in PATTERNS.items():
if pattern.match(value):
try:
datetime.strptime(value, FORMAT_STRINGS[name])
return True
except ValueError:
return False # format match but calendar invalid
return FalseHandling Optional Leading Zeros
Some systems emit dates like 2026-8-5 without leading zeros. To accept both zero-padded and non-padded variants, replace 0[1-9] with 0?[1-9]:
// Accept both 2026-08-05 and 2026-8-5
/^\d{4}-(0?[1-9]|1[0-2])-(0?[1-9]|[12]\d|3[01])$/ISO 8601 Datetime with Time
// Matches: 2026-08-05T14:30:00Z, 2026-08-05T14:30:00+05:30
const ISO_DATETIME = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T(0\d|1\d|2[0-3]):([0-5]\d):([0-5]\d)(?:\.\d{1,9})?(?:Z|[+-](0\d|1[0-4]):[0-5]\d)$/;Key Decision Points
| Question | Recommendation |
|---|---|
| Must catch Feb 30? | Add real date parse after regex |
| Multiple formats? | Use alternation or try each pattern |
| API input validation? | ISO 8601 only — reject all other formats |
| User-facing form? | Use a date picker instead of text regex |