const regex = /(?=(?:^(?:\d\d\d\d-(?:0[1-9]|10|11|12)-(?:0[1-9]|1[0-9]|2[0-8])|\d\d\d\d-(?:0[13-9]|10|11|12)-(?:29|30)|\d\d\d\d-(?:0[13578]|10|12)-31|(?:\d\d[2468][048]|\d\d0[48]|\d\d[13579][26])-02-29|(?:[02468][048]00|[13579][26]00)-02-29)T(?:(?:0[0-9]|1[0-9]|2[0-3]):(?:[0-5][0-9]):(?:[0-5][0-9]))(?:\.\d\d\d)?(?:Z|[\+\-](?:0[0-9]|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$|^(?:197[2-9]|1987|1989|199[058]|2005|2008|2016)-12-31T23:59:60Z$)(?!.*-00:00$)^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(?:\.(\d\d\d))?((Z)|([\+\-])(\d\d):(\d\d))$/gm;
// Alternative syntax using RegExp constructor
// const regex = new RegExp('(?=(?:^(?:\\d\\d\\d\\d-(?:0[1-9]|10|11|12)-(?:0[1-9]|1[0-9]|2[0-8])|\\d\\d\\d\\d-(?:0[13-9]|10|11|12)-(?:29|30)|\\d\\d\\d\\d-(?:0[13578]|10|12)-31|(?:\\d\\d[2468][048]|\\d\\d0[48]|\\d\\d[13579][26])-02-29|(?:[02468][048]00|[13579][26]00)-02-29)T(?:(?:0[0-9]|1[0-9]|2[0-3]):(?:[0-5][0-9]):(?:[0-5][0-9]))(?:\\.\\d\\d\\d)?(?:Z|[\\+\\-](?:0[0-9]|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$|^(?:197[2-9]|1987|1989|199[058]|2005|2008|2016)-12-31T23:59:60Z$)(?!.*-00:00$)^(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)T(\\d\\d):(\\d\\d):(\\d\\d)(?:\\.(\\d\\d\\d))?((Z)|([\\+\\-])(\\d\\d):(\\d\\d))$', 'gm')
const str = `Strong Matcher for ISO 8601 / RFC 3339 Date Times; rejects bad TZ offsets, illegal times (Museum of Bad Data)
See below for expanded matcher
\`\`\`
// accept: Exemplars
2008-02-03T04:05:06.007Z
2008-02-03T04:05:06Z
0000-01-01T00:00:00.000Z
0000-02-29T04:05:06.007Z
9999-12-31T23:59:59.999Z
9999-12-31T23: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:06.007Z
0008-02-29T04:05:06.007Z
0012-02-29T04:05:06.007Z
0016-02-29T04:05:06.007Z
0020-02-29T04:05:06.007Z
0024-02-29T04:05:06.007Z
0028-02-29T04:05:06.007Z
0032-02-29T04:05:06.007Z
0036-02-29T04:05:06.007Z
0040-02-29T04:05:06.007Z
0044-02-29T04:05:06.007Z
0048-02-29T04:05:06.007Z
0052-02-29T04:05:06.007Z
0056-02-29T04:05:06.007Z
0060-02-29T04:05:06.007Z
0064-02-29T04:05:06.007Z
0068-02-29T04:05:06.007Z
0072-02-29T04:05:06.007Z
0076-02-29T04:05:06.007Z
0080-02-29T04:05:06.007Z
0084-02-29T04:05:06.007Z
0088-02-29T04:05:06.007Z
0092-02-29T04:05:06.007Z
0096-02-29T04:05:06.007Z
1560-02-29T04:05:06.007Z
2004-02-29T04:05:06.007Z
2008-02-29T04:05:06.007Z
2012-02-29T04:05:06.007Z
2016-02-29T04:05:06.007Z
2020-02-29T04:05:06.007Z
2024-02-29T04:05:06.007Z
2028-02-29T04:05:06.007Z
2032-02-29T04:05:06.007Z
2036-02-29T04:05:06.007Z
2040-02-29T04:05:06.007Z
2044-02-29T04:05:06.007Z
9996-02-29T04:05:06.007Z
// accept: Leap day, year is Multiple of 400
0000-02-29T04:05:06.007Z
0400-02-29T04:05:06.007Z
0800-02-29T04:05:06.007Z
1200-02-29T04:05:06.007Z
1600-02-29T04:05:06.007Z
2000-02-29T04:05:06.007Z
2400-02-29T04:05:06.007Z
2800-02-29T04:05:06.007Z
3200-02-29T04:05:06.007Z
3600-02-29T04:05:06.007Z
4000-02-29T04:05:06.007Z
4400-02-29T04:05:06.007Z
4800-02-29T04:05:06.007Z
5200-02-29T04:05:06.007Z
5600-02-29T04:05:06.007Z
6000-02-29T04:05:06.007Z
6400-02-29T04:05:06.007Z
6800-02-29T04:05:06.007Z
7200-02-29T04:05:06.007Z
7600-02-29T04:05:06.007Z
8000-02-29T04:05:06.007Z
8400-02-29T04:05:06.007Z
8800-02-29T04:05:06.007Z
9200-02-29T04:05:06.007Z
9600-02-29T04:05:06.007Z
// accept: Day in range for month
2008-01-30T04:05:06.007Z
2008-03-30T04:05:06.007Z
2008-04-30T04:05:06.007Z
2008-05-30T04:05:06.007Z
2008-06-30T04:05:06.007Z
2008-07-30T04:05:06.007Z
2008-08-30T04:05:06.007Z
2008-09-30T04:05:06.007Z
2008-10-30T04:05:06.007Z
2008-11-30T04:05:06.007Z
2008-12-30T04:05:06.007Z
2008-01-31T04:05:06.007Z
2008-03-31T04:05:06.007Z
2008-05-31T04:05:06.007Z
2008-07-31T04:05:06.007Z
2008-08-31T04:05:06.007Z
2008-10-31T04:05:06.007Z
2008-12-31T04:05:06.007Z
// accept: had 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: Year out of range
10000-02-29T04:05:06.007Z
// REJECT: Month out of range
2008-00-30T04:05:06.007Z
2008-13-30T04:05:06.007Z
2008-14-30T04:05:06.007Z
2008-20-30T04:05:06.007Z
// REJECT: Day out of range for month
2008-02-30T04:05:06.007Z
2008-04-31T04:05:06.007Z
2008-06-31T04:05:06.007Z
2008-09-31T04:05:06.007Z
2008-11-31T04:05:06.007Z
2008-02-31T04:05:06.007Z
// REJECT: Day out of range
2008-12-32T04:05:06.007Z
2008-12-99T04:05:06.007Z
2008-12-00T04:05:06.007Z
// REJECT: Hour out of range
2008-12-08T60:05:06.007Z
2008-12-08T99:05:06.007Z
// REJECT: Minute out of range
2008-12-08T04:60:06.007Z
2008-12-08T04:99:06.007Z
// REJECT: Seconds out of range
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:06.007Z
-0400-02-29T04:05:06.007Z
-0100-02-29T04:05:06.007Z
-0004-02-29T04:05:06.007Z
// REJECT: stray characters
2008-12-31T04:05:06.007Z
2008-12-31T04:05:06.007Z
2008-12-31T04:05:06.007 Z
// REJECT: Malformed
2008-02-0304:05:06.007Z
20080203T040506.007Z
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
1973-06-30T23:59:60Z
1974-06-30T23:59:60Z
1975-06-30T23:59:60Z
1976-06-30T23:59:60Z
1977-06-30T23:59:60Z
1978-06-30T23:59:60Z
1979-06-30T23:59:60Z
1980-06-30T23:59:60Z
1984-06-30T23:59:60Z
1986-06-30T23:59:60Z
1987-06-30T23:59:60Z
1988-06-30T23:59:60Z
1989-06-30T23:59:60Z
1990-06-30T23:59:60Z
1991-06-30T23:59:60Z
1995-06-30T23:59:60Z
1996-06-30T23:59:60Z
1998-06-30T23:59:60Z
1999-06-30T23:59:60Z
2000-06-30T23:59:60Z
2001-06-30T23:59:60Z
2002-06-30T23:59:60Z
2003-06-30T23:59:60Z
2004-06-30T23:59:60Z
2005-06-30T23:59:60Z
2006-06-30T23:59:60Z
2007-06-30T23:59:60Z
2008-06-30T23:59:60Z
2009-06-30T23:59:60Z
2010-06-30T23:59:60Z
2011-06-30T23:59:60Z
2013-06-30T23:59:60Z
2014-06-30T23:59:60Z
2016-06-30T23:59:60Z
2017-06-30T23:59:60Z
2018-06-30T23:59:60Z
2019-06-30T23:59:60Z
2020-06-30T23:59:60Z
2021-06-30T23:59:60Z
2022-06-30T23:59:60Z
2023-06-30T23:59:60Z
1980-12-31T23:59:60Z
1981-12-31T23:59:60Z
1982-12-31T23:59:60Z
1983-12-31T23:59:60Z
1984-12-31T23:59:60Z
1985-12-31T23:59:60Z
1986-12-31T23:59:60Z
1988-12-31T23:59:60Z
1991-12-31T23:59:60Z
1992-12-31T23:59:60Z
1993-12-31T23:59:60Z
1994-12-31T23:59:60Z
1996-12-31T23:59:60Z
1997-12-31T23:59:60Z
1999-12-31T23:59:60Z
2000-12-31T23:59:60Z
2001-12-31T23:59:60Z
2002-12-31T23:59:60Z
2003-12-31T23:59:60Z
2004-12-31T23:59:60Z
2006-12-31T23:59:60Z
2007-12-31T23:59:60Z
2009-12-31T23:59:60Z
2010-12-31T23:59:60Z
2011-12-31T23:59:60Z
2012-12-31T23:59:60Z
2013-12-31T23:59:60Z
2014-12-31T23:59:60Z
2015-12-31T23:59:60Z
2017-12-31T23:59:60Z
2018-12-31T23:59:60Z
2019-12-31T23:59:60Z
2020-12-31T23:59:60Z
2021-12-31T23:59:60Z
2022-12-31T23:59:60Z
2023-12-31T23: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, year is not a multiple of four
0101-02-29T04:05:06.007Z
0102-02-29T04:05:06.007Z
0103-02-29T04:05:06.007Z
1537-02-29T04:05:06.007Z
1538-02-29T04:05:06.007Z
2001-02-29T04:05:06.007Z
2002-02-29T04:05:06.007Z
2003-02-29T04:05:06.007Z
2021-02-29T04:05:06.007Z
2022-02-29T04:05:06.007Z
2023-02-29T04:05:06.007Z
2025-02-29T04:05:06.007Z
2026-02-29T04:05:06.007Z
2027-02-29T04:05:06.007Z
2029-02-29T04:05:06.007Z
2030-02-29T04:05:06.007Z
2031-02-29T04:05:06.007Z
2033-02-29T04:05:06.007Z
2034-02-29T04:05:06.007Z
2035-02-29T04:05:06.007Z
2037-02-29T04:05:06.007Z
2038-02-29T04:05:06.007Z
2039-02-29T04:05:06.007Z
2041-02-29T04:05:06.007Z
2042-02-29T04:05:06.007Z
2043-02-29T04:05:06.007Z
9997-02-29T04:05:06.007Z
9998-02-29T04:05:06.007Z
9999-02-29T04:05:06.007Z
// REJECT: Not a leap day, year is a multiple of 100 but not 400
0100-02-29T04:05:06.007Z
0200-02-29T04:05:06.007Z
0300-02-29T04:05:06.007Z
0500-02-29T04:05:06.007Z
0600-02-29T04:05:06.007Z
0700-02-29T04:05:06.007Z
0900-02-29T04:05:06.007Z
1000-02-29T04:05:06.007Z
1100-02-29T04:05:06.007Z
1500-02-29T04:05:06.007Z
2100-02-29T04:05:06.007Z
2200-02-29T04:05:06.007Z
2300-02-29T04:05:06.007Z
2500-02-29T04:05:06.007Z
2600-02-29T04:05:06.007Z
2700-02-29T04:05:06.007Z
2900-02-29T04:05:06.007Z
3000-02-29T04:05:06.007Z
3100-02-29T04:05:06.007Z
3300-02-29T04:05:06.007Z
3400-02-29T04:05:06.007Z
3500-02-29T04:05:06.007Z
3700-02-29T04:05:06.007Z
\`\`\`
\`\`\`
(?=
(?:^
(?: # All non-leap-second YYYY-MM-DD parts:
(?:\\d\\d\\d\\d-(?:0[1-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][048]|\\d\\d0[48]|\\d\\d[13579][26])-02-29 # leap years not divisible by 100
| (?:[02468][048]00|[13579][26]00)-02-29 # leap years divisible by 400
)
T
(?:(?:0[0-9]|1[0-9]|2[0-3]):(?:[0-5][0-9]):(?:[0-5][0-9])) # time part
(?:\\.\\d\\d\\d)? # ms part (optional)
(?:Z|[\\+\\-](?:0[0-9]|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))\$
\`\`\``;
// Reset `lastIndex` if this regex is defined globally
// regex.lastIndex = 0;
let m;
while ((m = regex.exec(str)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
// The result can be accessed through the `m`-variable.
m.forEach((match, groupIndex) => {
console.log(`Found match, group ${groupIndex}: ${match}`);
});
}
Please keep in mind that these code samples are automatically generated and are not guaranteed to work. If you find any syntax errors, feel free to submit a bug report. For a full regex reference for JavaScript, please visit: https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions