Regex to match an ISO 8601 datetime string
Asked Answered
B

13

99

does anyone have a good regex pattern for matching iso datetimes?

ie: 2010-06-15T00:00:00

Bloxberg answered 29/6, 2010 at 17:13 Comment(1)
i use /^(\d{4})-0?(\d+)-0?(\d+)[T ]0?(\d+):0?(\d+):0?(\d+)$/, (which however is not the most strict one) .. conversion to the Date is a different story :)Photophobia
O
166

For the strict, full datetime, including milliseconds, per the W3C's take on the spec.:

//-- Complete precision:
/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/

//-- No milliseconds:
/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)/

//-- No Seconds:
/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)/

//-- Putting it all together:
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/

.
Additional variations allowed by the actual ISO 8601:2004(E) doc:

/********************************************
**    No time-zone varients:
*/
//-- Complete precision:
/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+/

//-- No milliseconds:
/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d/

//-- No Seconds:
/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d/

//-- Putting it all together:
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+)|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d)|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d)/

WARNING: This all gets messy fast, and it still allows certain nonsense such as a 14th month. Additionally, ISO 8601:2004(E) allows a several other variants.

.
"2010-06-15T00:00:00" isn't legal, because it doesn't have the time-zone designation.

Orpiment answered 29/6, 2010 at 17:34 Comment(15)
ISO-8601 says that if the timezone is omitted it's assumed to be UTC, this includes the 'Z'.Clubfoot
@Scott S. McCoy: I went off the W3C's interpretation (w3.org/TR/NOTE-datetime) like a good web-developer should. ;-) I also don't consider Wikipedia to be a definitive source. But, you are right. According to the actual spec (dotat.at/tmp/ISO_8601-2004_E.pdf), the timezone offset is optional.Orpiment
I wholeheartedly agree that Wikipedia is not a definitive source. But for some topics it makes a reasonable reference, and the article on ISO-8601 has palatable examples and digestible explanations. :-DClubfoot
Negative years are allowed so there should be a optional '-' character before years, see w3.org/TR/xmlschema-2/#dateTime : The ·lexical space· of dateTime consists of finite-length sequences of characters of the form: '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?Inwards
ISO 8601 states that the 'T' may be omitted under some circumstances. Also, RFC 3389 extends ISO 8601 tools.ietf.org/html/rfc3339#page-12 , which allows for a space character in place of the 'T'.Inwards
Wow. This answer is referenced in the angular source code! @BrockAdamsLavelle
@sidonaldson, Thanks for the notice! I'm rather conflicted though, as I suspect that regex is not the optimal way to validate something as messy as date formats. (But this Q asked for regex and presumably the OP can live with the computational cost and small accuracy holes -- while we wouldn't want that in a library, language, or app engine.)Orpiment
Thanks for the regex. To tweak it a bit, we can use (?: ) instead of ( ) to avoid capturing a group. e.g. \d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z) for the first regex.Darken
@Thanish, yes you can. The capture group normally doesn't hurt anything, though, and can optionally help you determine which style was matched, if you need to for some reason.Orpiment
Regex escaped (for the first regex): expect(value).toMatch('\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+(?:[+-][0-2]\\d:[0-5]\\d|Z)');Homebred
This will fail for leap seconds, i.e, that occasional 61st second.Sagesagebrush
this allows strange things to be valid as: 1881-16-27T11:06:38.9986346665253151+01:34 try this in Chrome console to check it: /(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/.test("1881-16-27T11:06:38.9986346665253151+01:34");Ayannaaycock
Moment.js doesn't like tailing 'Z'. It says 'it is not in a recognized RFC2822 or ISO format'Peterus
Brilliant work! Both T, t and space are valid time separators. Following would have those options as well. /(\d{4}-[01]\d-[0-3]\d[Tt\s][0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\d[Tt\s][0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\d[Tt\s][0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/Marcenemarcescent
Coming here to say that this regex is incorrect and fails on "2023-08-15T18:17:40.0000000". This JSFiddle, however, succeeds: jsfiddle.net/wq7tjec7/14Bein
A
24

UPDATED to address issues mentioned in comments. Now it does not accept 00 as days or months. Though, invalid dates like 2023-02-29, 2023-02-30, 2023-02-31 are accepted by this regexp. IMO, it's not a task of regexp to validate the date. Instead, it's needed to to ensure that format is likely to be a date.

For matching just ISO date, like 2017-09-22, you can use this regexp:

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

It will match any numeric year, any month specified by two digits in range 01-12 and any date specified by two digits in range 01-31

Appalachia answered 22/9, 2017 at 10:10 Comment(4)
How is 00 valid for either month or day?Tatary
It's as valid as 2023-02-30. Ultimately regex is unsuitable for validating dates are correct and should only be used to indicate the expected format. Given that, a shorter format that is easier to read but allows some obvious mistakes is a good compromise. I actually prefer ^\d{4}-\d{2}-\d{2}$ because of that.Honkytonk
Wouldn't it have been better to specify [1-9] instead of \d for months, to catch the case of a zero'th month? Though I'm not sure in how far a RegEx should verify correctness, instead of just the general format, in which case @Caoilte's simpler RegEx would do.Sherysherye
@YigitErol fixed. Thanks for notingAppalachia
U
12

I reworked the top answer into something a bit more concise. Instead of writing out each of the three optional patterns, the elements are nested as optional statements.

/[+-]?\d{4}(-[01]\d(-[0-3]\d(T[0-2]\d:[0-5]\d:?([0-5]\d(\.\d+)?)?[+-][0-2]\d:[0-5]\dZ?)?)?)?/

I'm curious if there are downsides to this approach?

You can find tests for my suggested answer here: http://regexr.com/3e0lh

Union answered 12/8, 2016 at 17:30 Comment(3)
This won't work with: 2016-09-05T15:22:26.286Z Making [+-][0-2]\d:[0-5]\d optional makes it work: [+-]?\d{4}(-[01]\d(-[0-3]\d(T[0-2]\d:[0-5]\d:?([0-5]\d(\.\d+)?)?([+-][0-2]\d:[0-5]\d)?Z?)?)?)?Goode
this allows strange things to be valid as: 28T09:5220.5690463826472487295963210360122513980205942+01:53 try this in Chrome console to check it: /[+-]?\d{4}(-[01]\d(-[0-3]\d(T[0-2]\d:[0-5]\d:?([0-5]\d(\.\d+)?)?[+-][0-2]\d:[0-5]\dZ?)?)?)?/.test("28T09:5220.5690463826472487295963210360122513980205942+01:53");Ayannaaycock
This caused my auth token to be caught because it had a matching part matched by the section \d{4}.Dipietro
A
12

I have made this regex and solves the validation for dates as they come out of Javascript's .toISOString() method.

^[0-9]{4}-((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01])|(0[469]|11)-(0[1-9]|[12][0-9]|30)|(02)-(0[1-9]|[12][0-9]))T(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9]):(0[0-9]|[1-5][0-9])\.[0-9]{3}Z$

Contemplated:

  • Proper symbols ('-', 'T', ':', '.', 'Z') in proper places.
  • Consistency with months of 29, 30 or 31 days.
  • Hours from 00 to 23.
  • Minutes and seconds from 00 to 59.
  • Milliseconds from 000 to 999.

Not contemplated:

  • Leap years.

Example date: 2019-11-15T13:34:22.178Z

Example to run directly in Chrome console: /^[0-9]{4}-((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01])|(0[469]|11)-(0[1-9]|[12][0-9]|30)|(02)-(0[1-9]|[12][0-9]))T(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9]):(0[0-9]|[1-5][0-9])\.[0-9]{3}Z$/.test("2019-11-15T13:34:22.178Z");

Regex flow diagram (Regexper): regex flow diagram

Ayannaaycock answered 15/11, 2019 at 13:51 Comment(5)
Why a such complex regex if it doesn't validate leap years? neither leap second.Goniometer
@Goniometer Because is the best between the ones posted previously. It respects each item I have pointed in the "contemplates" section. Try the regex in this answer and the other posted previously using this site, and you will see what I'm talking about. (I have also commented the problems in each of the other regex posted showing why them have flaws)Ayannaaycock
Sorry but it's not better because it doesn't handle leap years.Goniometer
It is better because the others does not make what they promise. Please, try the other regex from this different answers with the page I have suggested in the previous comment and you will see what I am trying to explain.Ayannaaycock
I think this solution (even though it does not check for leap years nor seconds), however, it could be simplified to /^[1-9]\d{3}-((0[13578]|1[02])-([0-2]\d|31)|([469]|11)-([0-2]\d|31)|02-[0-2]\d)T([01](0-9]|2[0-3])):[0-5]\d\.\d{3}Z$/. Note that I have just found out that Node.js (test with [email protected]) supports only years between 1000 and 9999, therefore I updated the regex accordingly. This is also true for Firefox Nightly web console.Corcoran
G
7

Here is a regular expression to check ISO 8601 date format including leap years and short-long months. To run this, you'll need to "ignore white-space". A compacted version without white-space is on regexlib: http://regexlib.com/REDetails.aspx?regexp_id=3344

There's more to ISO 8601 - this regex only cares for dates, but you can easily extend it to support time validation which is not that tricky.

Update: This works now with javascript (without lookbehinds)

  ^(?:
      (?=
            [02468][048]00
            |[13579][26]00
            |[0-9][0-9]0[48]
            |[0-9][0-9][2468][048]
            |[0-9][0-9][13579][26]              
      )

      \d{4}

      (?:

        (-|)

        (?:

            (?:
                00[1-9]
                |0[1-9][0-9]
                |[1-2][0-9][0-9]
                |3[0-5][0-9]
                |36[0-6]
            )
            |
                (?:01|03|05|07|08|10|12)
                (?:
                  \1
                  (?:0[1-9]|[12][0-9]|3[01])
                )?            
            |
                (?:04|06|09|11)
                (?:
                  \1
                  (?:0[1-9]|[12][0-9]|30)
                )?            
            |
                02
                (?:
                  \1
                  (?:0[1-9]|[12][0-9])
                )?

            |
                W(?:0[1-9]|[1-4][0-9]|5[0-3])
                (?:
                  \1
                  [1-7]
                )?

        )            
      )?
  )$
  |
  ^(?:
      (?!
            [02468][048]00
            |[13579][26]00
            |[0-9][0-9]0[48]
            |[0-9][0-9][2468][048]
            |[0-9][0-9][13579][26]              
      )

      \d{4}

      (?:

        (-|)

        (?:

            (?:
                00[1-9]
                |0[1-9][0-9]
                |[1-2][0-9][0-9]
                |3[0-5][0-9]
                |36[0-5]
            )
            |
                (?:01|03|05|07|08|10|12)
                (?:
                  \2
                  (?:0[1-9]|[12][0-9]|3[01])
                )?

            |
                (?:04|06|09|11)
                (?:
                  \2
                  (?:0[1-9]|[12][0-9]|30)
                )?
            |
                (?:02)
                (?:
                  \2
                  (?:0[1-9]|1[0-9]|2[0-8])
                )?
            |
                W(?:0[1-9]|[1-4][0-9]|5[0-3])
                (?:
                  \2
                  [1-7]
                )?
       ) 
    )?
)$

To cater for time, add something like this to the mixture (from: http://underground.infovark.com/2008/07/22/iso-date-validation-regex/ ):

([T\s](([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)?(\15([0-5]\d))?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?
Glottology answered 28/8, 2011 at 14:30 Comment(0)
C
4

The ISO 8601 specification allows a wide variety of date formats. There's a mediocre explanation as to how to do it here. There is a fairly minor discrepancy between how Javascript's date input formatting and the ISO formatting for simple dates which do not specify timezones, and it can be easily mitigated using a string substitution. Fully supporting the ISO-8601 specification is non-trivial.

Here is a reference example which I do not guarantee to be complete, although it parses the non-duration dates from the aforementioned Wikipedia page.

Below is an example, and you can also see it's output on ideone. Unfortunately, it does not work to specification as it does not properly implement weeks. The definition of the week number 01 in ISO-8601 is non-trivial and requires some browsing the calendar to determine where week one begins, and what exactly it means in terms of the number of days in the specified year. This can probably be fairly easily corrected (I'm just tired of playing with it).

function parseISODate (input) {
    var iso = /^(\d{4})(?:-?W(\d+)(?:-?(\d+)D?)?|(?:-(\d+))?-(\d+))(?:[T ](\d+):(\d+)(?::(\d+)(?:\.(\d+))?)?)?(?:Z(-?\d*))?$/;

    var parts = input.match(iso);

    if (parts == null) {
        throw new Error("Invalid Date");
    }

    var year = Number(parts[1]);

    if (typeof parts[2] != "undefined") {
        /* Convert weeks to days, months 0 */
        var weeks = Number(parts[2]) - 1;
        var days  = Number(parts[3]);

        if (typeof days == "undefined") {
            days = 0;
        }

        days += weeks * 7;

        var months = 0;
    }
    else {
        if (typeof parts[4] != "undefined") {
            var months = Number(parts[4]) - 1;
        }
        else {
            /* it's an ordinal date... */
            var months = 0;
        }

        var days   = Number(parts[5]);
    }

    if (typeof parts[6] != "undefined" &&
        typeof parts[7] != "undefined")
    {
        var hours        = Number(parts[6]);
        var minutes      = Number(parts[7]);

        if (typeof parts[8] != "undefined") {
            var seconds      = Number(parts[8]);

            if (typeof parts[9] != "undefined") {
                var fractional   = Number(parts[9]);
                var milliseconds = fractional / 100;
            }
            else {
                var milliseconds = 0
            }
        }
        else {
            var seconds      = 0;
            var milliseconds = 0;
        }
    }
    else {
        var hours        = 0;
        var minutes      = 0;
        var seconds      = 0;
        var fractional   = 0;
        var milliseconds = 0;
    }

    if (typeof parts[10] != "undefined") {
        /* Timezone adjustment, offset the minutes appropriately */
        var localzone = -(new Date().getTimezoneOffset());
        var timezone  = parts[10] * 60;

        minutes = Number(minutes) + (timezone - localzone);
    }

    return new Date(year, months, days, hours, minutes, seconds, milliseconds);
}

print(parseISODate("2010-06-29T15:33:00Z-7"))
print(parseISODate("2010-06-29 06:14Z"))
print(parseISODate("2010-06-29T06:14Z"))
print(parseISODate("2010-06-29T06:14:30.2034Z"))
print(parseISODate("2010-W26-2"))
print(parseISODate("2010-180"))
Clubfoot answered 29/6, 2010 at 23:5 Comment(0)
M
4

yyyy-MM-dd

Too much explanation for most of the answers here, here's a short variation of @Sergey answer addressing some weird scenarios (like 2020-00-00), this RegExp only cares about the yyyy-MM-dd date:

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

Also this one doesn't care about the number of days per month, like 2020-11-31 (because November has only 30 days).

My use-case was to convert a String into a Date (from an API param) and I needed only to know that the input string didn't contained strange stuff, I do the next validation against an actual Date object.

Mesozoic answered 20/11, 2020 at 19:59 Comment(0)
K
4

Here is my take on this:

^\d{4}-(?:0[1-9]|1[0-2])-(?:[0-2][1-9]|[1-3]0|3[01])T(?:[0-1][0-9]|2[0-3])(?::[0-6]\d)(?::[0-6]\d)?(?:\.\d{3})?(?:[+-][0-2]\d:[0-5]\d|Z)?$

Examples for a match:

2016-12-31T23:59:60+12:30
2021-05-10T09:05:12.000Z
3015-01-01T23:00+02:00
1001-01-31T23:59:59Z
2023-12-20T20:20

The minutes and seconds part could be refined more, but this is good enough for me.

Regexper

enter image description here

Kelikeligot answered 13/1, 2023 at 15:56 Comment(0)
B
1

Not sure if it's relevant to the underlying problem you are trying to solve, but you can pass an ISO date string as a constructor arg to Date() and get an object out of it. The constructor is actually very flexible in terms of coercing a string into a Date.

Bookerbookie answered 29/6, 2010 at 17:42 Comment(1)
Confirmed, that approach doesn't work. The problem is with IE. 2014-06-44 becomes 2014-08-13 as IE treats the overflowing date (>30) as a date the next month. I've tried in IE8 and IE11. Works in Chrome though. Very annoying.Gesualdo
N
1

with 02/29 validation from the year 1900 to 2999

 (((2000|2400|2800|((19|2[0-9])(0[48]|[2468][048]|[13579][26])))-02-29)|(((19|2[0-9])[0-9]{2})-02-(0[1-9]|1[0-9]|2[0-8]))|(((19|2[0-9])[0-9]{2})-(0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01]))|(((19|2[0-9])[0-9]{2})-(0[469]|11)-(0[1-9]|[12][0-9]|30)))T([01][0-9]|[2][0-3]):[0-5][0-9]:[0-5][0-9]\.[0-9]{3}Z
Notornis answered 6/3, 2019 at 14:53 Comment(1)
this regex allows invalid dates as: 19-02-29T20:59:39.217Z where the year doesn't respect ISO format yyyy you can try this in Chrome console to check it: /(((2000|2400|2800|(19|2[0-9](0[48]|[2468][048]|[13579][26])))-02-29)|(((19|2[0-9])[0-9]{2})-02-(0[1-9]|1[0-9]|2[0-8]))|(((19|2[0-9])[0-9]{2})-(0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01]))|(((19|2[0-9])[0-9]{2})-(0[469]|11)-(0[1-9]|[12][0-9]|30)))T([01][0-9]|[2][0-3]):[0-5][0-9]:[0-5][0-9]\.[0-9]{3}Z/.test("19-02-29T20:59:39.217Z");Ayannaaycock
P
1

Brocks answers are good, but should start with ^ and end with $ so as not to allow prefix/suffix characters if all you are trying to match is the date string alone.

Peters answered 5/8, 2021 at 0:54 Comment(0)
T
1

While using QRegExp with IsoDateWithMs the millisecond ones here did not work. instead the following saved the day.

\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d{1,3}

(I know this is a JS entry but it pops up first and would be helpful for c++ devs)

Talie answered 18/11, 2022 at 11:12 Comment(0)
N
0

If you need only verify string like 2020-01-01, then code below might be useful:

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

First number should have any 4 digits, month 01 - 12 and day 01 - 31.

This does not render wrong 2020-02-31, which is actually invalid data. But renders wrong 2021-13-32 or 2021-00-00.

But this might be useful for quick validation, that data matches the format.

Noll answered 16/4, 2024 at 15:44 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.