Community Patterns

1

Strong Matcher for ISO 8601 / RFC 3339 Date Times; rejects bad TZ offsets, illegal times (Museum of Bad Data)

Created·2023-05-14 07:19
Flavor·ECMAScript (JavaScript)
Handles many nuanced cases around time zone offsets, leap seconds and leap days. References: List of UTC Offsets RFC 3339, the stricter rules that most systems use in practice ISO 8601, the widely known name for this format Leap Year Leap Second Caveats: Rejects -00:00 timezone offset. rejects all future leap seconds, and only parses ones with a 'Z' offsets. Pattern (?= (?:^ (?: # All non-leap-second YYYY-MM-DD parts: \d\d\d\d-(?:01-9] |10|11|12)-(?:0[1-9]|1[0-9]|2[0-8]) # Days 01-28 | \d\d\d\d-(?:0[13-9] |10|11|12)-(?:29|30) # Days 29+30 | \d\d\d\d-(?:0[13578]|10 |12)-31 # Day 31 | (?:\d\d[2468|\d\d048]|\d\d[13579)-02-29 # leap years not divisible by 100 | (?:0246800|1357900)-02-29 # leap years divisible by 400 ) T (?:(?:00-9]|1[0-9]|2[0-3]):(?:[0-5):(?:0-5)) # time part (?:\.\d\d\d)? # ms part (optional) (?:Z|\+\-|1[012]):00|\+0[34569]:30|\+10:30|-0[39]:30|\+1[34]:00|\+0[58]:45|\+12:45) $) |^(?:1972 |198[1235]|199[2347]|2012|2015 )-06-30T23:59:60Z$ # all june leapsecs |^(?:197[2-9]|1987|1989|199[058] |2005|2008|2016)-12-31T23:59:60Z$ # all december leapsecs ) (?!.*-00:00$) # if given a -00:00 offset reject unconditionally, psychhhh OK! Since only valid times are now possible, we can use a loose pattern match to parse. ^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(?:\.(\d\d\d))?((Z)|([\+\-])(\d\d):(\d\d))$ Test cases // accept: Exemplars 2008-02-03T04:05:06.007Z 2008-02-03T04:05:06Z 0000-01-01T00:00:00.000Z 9999-12-31T59:59:59.999Z 9999-12-31T59:59:59Z 2008-02-03T04:05:06.007+12:45 2008-02-03T04:05:06+03:30 0000-01-01T00:00:00.007-09:30 0000-02-29T04:05:06.007+14:00 9999-12-31T23:59:59.999+12:00 9999-12-31T23:59:59-12:00 // accept: Leap day, year is multiple of four 0004-02-29T04:05:06Z 0096-02-29T04:05:06Z 1560-02-29T04:05:06Z 2004-02-29T04:05:06Z 2020-02-29T04:05:06Z 2032-02-29T04:05:06Z 9996-02-29T04:05:06Z // accept: Leap day, year is Multiple of 400 0000-02-29T04:05:06Z 1200-02-29T04:05:06Z 1600-02-29T04:05:06Z 2000-02-29T04:05:06Z 3600-02-29T04:05:06Z 8000-02-29T04:05:06Z 9600-02-29T04:05:06Z // accept: Day in range for month 2008-01-30T04:05:06Z 2008-03-30T04:05:06Z 2008-04-30T04:05:06Z 2008-05-30T04:05:06Z 2008-06-30T04:05:06Z 2008-07-30T04:05:06Z 2008-08-30T04:05:06Z 2008-09-30T04:05:06Z 2008-10-30T04:05:06Z 2008-11-30T04:05:06Z 2008-12-30T04:05:06Z 2008-01-31T04:05:06Z 2008-02-31T04:05:06Z 2008-03-31T04:05:06Z 2008-05-31T04:05:06Z 2008-07-31T04:05:06Z 2008-08-31T04:05:06Z 2008-10-31T04:05:06Z 2008-12-31T04:05:06Z // accept: leap second 1972-06-30T23:59:60Z 1981-06-30T23:59:60Z 1982-06-30T23:59:60Z 1983-06-30T23:59:60Z 1985-06-30T23:59:60Z 1992-06-30T23:59:60Z 1993-06-30T23:59:60Z 1994-06-30T23:59:60Z 1997-06-30T23:59:60Z 2012-06-30T23:59:60Z 2015-06-30T23:59:60Z 1972-12-31T23:59:60Z 1973-12-31T23:59:60Z 1974-12-31T23:59:60Z 1975-12-31T23:59:60Z 1976-12-31T23:59:60Z 1977-12-31T23:59:60Z 1978-12-31T23:59:60Z 1979-12-31T23:59:60Z 1987-12-31T23:59:60Z 1989-12-31T23:59:60Z 1990-12-31T23:59:60Z 1995-12-31T23:59:60Z 1998-12-31T23:59:60Z 2005-12-31T23:59:60Z 2008-12-31T23:59:60Z 2016-12-31T23:59:60Z // REJECT: Out of range 10000-02-29T04:05:06Z 2008-00-30T04:05:06Z 2008-13-30T04:05:06Z 2008-02-30T04:05:06Z 2008-04-31T04:05:06Z 2008-06-31T04:05:06Z 2008-09-31T04:05:06Z 2008-11-31T04:05:06Z 2008-12-32T04:05:06Z 2008-12-99T04:05:06Z 2008-12-00T04:05:06Z // REJECT: Hour/min/sec out of range 2008-12-08T60:05:06Z 2008-12-08T04:60:06Z 2008-12-08T04:99:06Z 9999-12-31T59:59:61.999Z 2008-02-03T04:05:61.999Z 2008-02-03T04:05:61Z // REJECT: Negative dates not accepted -2000-02-29T04:05:06Z // REJECT: Malformed 2008-02-0304:05:06Z 20080203T040506Z 2008-02-03T04:05:06007Z 2008-02-03T04:05:06.7Z 2008-02-03T04:05:06.07Z 2008-02-03T04:05:06.0007Z 2008-02-03T04:05:06 2008-02-03T04:05:06.Z // REJECT: no leap second 1978-06-30T23:59:60Z // REJECT: assumes no future leap seconds 2024-12-31T23:59:60Z 2099-12-31T23:59:60Z 9999-12-31T23:59:60Z // REJECT: leap seconds only in UTC format 2005-12-31T23:59:60+00:00 2008-12-31T23:59:60+00:00 // REJECT: Not a leap day, not a multiple of four 2003-02-29T04:05:06Z 2037-02-29T04:05:06Z 2038-02-29T04:05:06Z 2039-02-29T04:05:06Z 9997-02-29T04:05:06Z 9998-02-29T04:05:06Z 9999-02-29T04:05:06Z // REJECT: Not a leap day, mult of 100 0100-02-29T04:05:06Z 1000-02-29T04:05:06Z 2100-02-29T04:05:06Z 2200-02-29T04:05:06Z 3500-02-29T04:05:06Z 3700-02-29T04:05:06Z
Submitted by Philip Flip Kromer (@mrflip)
1

Date - Extract & Validate - Fully tested - Format YYYY-MM-DD (dynamic parts separator / can use a different separator)

Created·2020-11-20 19:31
Flavor·ECMAScript (JavaScript)
A fully tested regex that extracts and validates date parts using named capturing groups. \ Validations: Year must be preceded by nothing or a non-digit character Year must have 4 digits Month must be between 01 and 12 Month must have 2 digits Day must be between 01 and the maximum number of days for the month (e.g. february can't have more than 29 days) Day must have 2 digits Day must be followed by nothing or a non-digit character Separator must be any single character that is not a space or an alphanumeric character Separator must be the same between each date part \ Capturing groups: | # | Name | Description | |:-:|:-------:|-------------------------------------| | 1 | year | 4 digits of the year | | 2 | sep | Date parts separator | | 3 | month | 2 digits of the month | | 4 | day | 2 digits of the date (day of month) | \ Example usage: let match = regex.exec('2020-11-22') console.log('year: %s, month: %s, day: %s', match.groups.year, match.groups.month, match.groups.day) // year: 2020, month: 11, day: 22 \ Compatibility: (updated 2020-11-20) Chrome >= 64 Edge >= 79 Firefox >= 78 IE incompatible (lookbehind assertions & named capture groups not supported) Opera >= 51 Safari incompatible (lookbehind assertions not supported) NodeJS >= 10.0.0 See regex compatibility table. \ Note: does not validate leap years (not really possible in regex)
Submitted by Elie Grenon (DrunkenPoney) <elie.grenon.1@gmail.com>

Community Library Entry

1

Regular Expression
Created·2025-11-05 00:24
Updated·2025-11-05 00:26
Flavor·PCRE2 (PHP)

/
^(?:(19[0-9]{2}|[2-9][0-9]{3})-(0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01])|(19[0-9]{2}|[2-9][0-9]{3})-(0[469]|11)-(0[1-9]|[12][0-9]|30)|(19[0-9]{2}|[2-9][0-9]{3})-(02)-(0[1-9]|1[0-9]|2[0-8])|((?:19|[2-9][0-9])(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))-(02)-(29))$
/
gm
Open regex in editor

Description

This regex validates dates in YYYY-MM-DD format with comprehensive rules:

  • Year: Must be greater than 1900 (1901-9999)
  • Month: Valid range 01-12
  • Day: Validates correct number of days per month:
    • 31 days: January, March, May, July, August, October, December
    • 30 days: April, June, September, November
    • 28 days: February (non-leap years)
    • 29 days: February 29th only in leap years

The regex captures year, month, and day in separate groups (positions vary based on which alternative matches).

Submitted by gh/barabasz