Regex for a YYYY-MM-DD date
^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$ checks the shape of an ISO 8601 date: four-digit year, month 01-12, day 01-31. It cannot know that February has 28 or 29 days, so 2023-02-30 passes. Parse the date to check that.
JavaScript ✓PCRE ✓
No catastrophic-backtracking patterns found. (A heuristic check: nested and adjacent repeats, overlapping alternatives.)
^Start of the stringanchor\dA digit (0-9)character type{4}Repeat it exactly 4 timesquantifier-The character '-' literallyliteral(?:…)Non-capturing group: groups without rememberinggroup|Either of 2 alternatives, tried left to right; the first that leads to an overall match winsalternation- Alternative 1:
0The character '0' literallyliteral[1-9]One character: '1' to '9'character class- Alternative 2:
1The character '1' literallyliteral[0-2]One character: '0' to '2'character class-The character '-' literallyliteral(?:…)Non-capturing group: groups without rememberinggroup|Either of 3 alternatives, tried left to right; the first that leads to an overall match winsalternation- Alternative 1:
0The character '0' literallyliteral[1-9]One character: '1' to '9'character class- Alternative 2:
[12]One character: '1' or '2'character class\dA digit (0-9)character type- Alternative 3:
3The character '3' literallyliteral[01]One character: '0' or '1'character class$End of the stringanchor
Examples
Checked at build time in JavaScript and in Python's re.
| Input | Matches? |
|---|---|
2024-02-29 | ✓ match |
1999-12-31 | ✓ match |
2024-13-01 | ✗ no match |
2024-00-10 | ✗ no match |
2024-1-5 | ✗ no match |
2024-04-31 | ✓ match |
2023-02-30 | ✓ match |
24-01-05 | ✗ no match |
2024/01/05 | ✗ no match |
2024-01-05T10:00:00Z | ✗ no match |
Try your own text
One string per line: each line is tested on its own (the m flag is added here).
How it works
Month is 0[1-9]|1[0-2] and day is 0[1-9]|[12]\d|3[01]. Both require two digits, which is what ISO 8601 and RFC 3339 specify. That rules out 2024-1-5.
Day validity depends on the month and, for February, on the leap-year rule (divisible by 4, except centuries not divisible by 400). You can encode that in a regex, but the result is unreadable. Use the regex to check the format, then parse. Watch out in JavaScript: new Date("2023-02-30") does not fail. V8 rolls it over to March 2. Compare the parsed parts back to the input, or use Python's date.fromisoformat, which raises ValueError on day 30 of February.
Variants
/^(\d{4})-(\d{2})-(\d{2})$/Shape plus capture groups. Pull out year, month and day, then validate them as numbers in code./^(?:0[1-9]|1[0-2])\/(?:0[1-9]|[12]\d|3[01])\/\d{4}$/US style MM/DD/YYYY. Same limits on day validity.
Gotchas
2024-04-31and2023-02-30match. April has 30 days and 2023 was not a leap year. Only parsing catches those.new Date("2024-01-05")is parsed as UTC midnight, butnew Date("2024-01-05T00:00")is local time. In US time zones the first one displays as January 4.- Year
0000matches. Most systems treat year 0 as invalid or as 1 BC. Use[1-9]\d{3}if that matters. - Full timestamps fail because of the
$. If you need the date part of an ISO timestamp, drop the$or match the time separately (see time). For Unix timestamps, see epoch time.
Related explainers
- Regex for a 24-hour time (HH:MM)A regex for 24-hour clock times from 00:00 to 23:59, with variants for optional seconds, single-digit...
- Regex for digits only, integers and decimalsRegexes for digits only, signed integers and decimal numbers, with the edge cases: leading zeros, a bare...
- Regex for a semantic version (SemVer)A readable regex for semantic versions like 1.2.3-beta.1+build.5, how it differs from the official...