Developer Toolbox

Regex: Date (YYYY-MM-DD)

The ISO 8601 date, as in 2026-10-03, sorts correctly as text and reads the same in every country. The pattern checks its shape: four digits for the year, a month from 01 to 12 and a day from 01 to 31, all with leading zeros.

Open in Regex Tester The pattern and every example below are filled in.

Pattern

^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$

How it works

^
Start of the string.
\d{4}
The year: four digits.
-
A dash.
(0[1-9]|1[0-2])
The month: 01 to 09, or 10 to 12.
-
A dash.
(0[1-9]|[12]\d|3[01])
The day: 01 to 09, 10 to 29, 30 or 31.
$
End of the string.

Matches

  • 2026-10-03
  • 1999-12-31
  • 2024-02-29

Doesn't match

  • 2026-13-01
  • 2026-1-5
  • 26-10-03
  • 2026-10-32

In your language

JavaScript
/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/

A literal; new RegExp(source, flags) builds the same from a string.

Python
re.compile(r"^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$", re.ASCII)

A raw string, so backslashes reach re as written. re.ASCII keeps \d to 0-9, as in JavaScript (Python matches any Unicode digit otherwise). Use re.fullmatch to test a whole string.

Java
Pattern.compile("^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$")

A normal string literal, so every backslash is doubled. matcher(s).matches() tests the whole string.

Go
regexp.MustCompile(`^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$`)

A raw string in backticks. RE2 runs in linear time but has no lookaround and no backreferences.

PHP
preg_match('/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/D', $input)

PCRE with / delimiters inside a single-quoted string. Without the D modifier, $ also matches before a final newline, so it is added to patterns that end in $.

C#
new Regex(@"^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$", RegexOptions.ECMAScript)

A verbatim string: backslashes stay, a quote is doubled. RegexOptions.ECMAScript keeps \d to 0-9, as in JavaScript. $ also matches before a final newline; to reject one, end the pattern with \z instead.

Common mistakes

  • February 31 passes

    The pattern does not know month lengths, so 2026-02-31 and 2025-02-29 match. Parse the string after matching: in Python, date.fromisoformat raises on an impossible date; in JavaScript, build the date and check that the month did not roll over.

  • ISO 8601 is wider than this

    Week dates (2026-W40-6), ordinal dates (2026-276) and dates with a time (2026-10-03T14:30:00Z) are ISO 8601 too. Accept only what your input should contain.

  • A date is not a moment

    Without a time zone, 2026-10-03 is a calendar day, not an instant. new Date("2026-10-03") in JavaScript reads it as midnight UTC, which is still the evening before in the Americas.