66

I have a DateTime string ISO8601 formated

2012-10-06T04:13:00+00:00

and the following Regex which does not match this string

#(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})\+(\d{2})\:(\d{2})#

I can't figure out why it does not match.

I escaped metacharacters, for me it seems to be OK.

http://jsfiddle.net/5n5vk/2/

EDIT :

The right way: http://jsfiddle.net/5n5vk/3/

Sean Bright
  • 118,630
  • 17
  • 138
  • 146
TwystO
  • 2,456
  • 2
  • 22
  • 28

6 Answers6

79

Incomplete Regex

It's incomplete as it matches invalid date such as 2013-99-99T04:13:00+00:00.

Better solution

The regex below won't match this kind of invalid date (cf. ISO 8601 Date Validation That Doesn’t Suck). You can test with the following code :

re = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/
var testDates = {
    'date' : "2012-10-06T04:13:00+00:00",
    'validDate' : "0785-10-10T04:13:00+00:00",
    'invalidDate' : "2013-99-99T04:13:00+00:00",
    '1234Date': '1234'
}
for (var d in testDates) {
    if (re.test(testDates[d])) { console.info('[valid]: '+testDates[d]); }
    else { console.error('[invalid]: '+testDates[d]); }
}
Édouard Lopez
  • 40,270
  • 28
  • 126
  • 178
  • thanks for referencing the article with the nice datetime pattern. – eeezyy Apr 23 '14 at 14:37
  • do you know how you can make date and time optional to match dates/times like this: `2012-10-06` or `04:13:00` and also the usual datetime `2012-10-06T04:13:00` I've tried it with conditions, but java doesn't support it. Group-backreferencing seems also not beeing supported in java. – eeezyy Apr 23 '14 at 14:38
  • This is a different question that can be interesting to others. Create a real question – Édouard Lopez Apr 27 '14 at 09:11
  • 7
    This regex is also incomplete since the specification states that you cannot mix the extended and basic formats (see ISO 8601:2004, section 4.3.2). "2009-01-31T230000-01:00" is an invalid ISO 8601 timestamp, yet this regex states that it's valid. – Ryan Smith May 13 '15 at 13:03
  • @RyanSmith would you mind to suggest an edit or propose your own answer? – Édouard Lopez May 18 '15 at 12:47
  • 2
    @ÉdouardLopez not really, I'm well aware of how difficult it is to get right. The validation we use on Learning Locker is on Github, I'm sure it's not complete but it works for us and passes our conformance tests. https://github.com/LearningLocker/StatementFactory/blob/master/src/Timestamp.php – Ryan Smith May 20 '15 at 10:11
  • 2
    https://jsfiddle.net/5n5vk/46/ @ÉdouardLopez – Ryan Smith Sep 09 '15 at 13:57
  • 3
    Only issue with that fiddle is that it validates 30th and 31st of February which is obvious non-sense and will validate 29th of February regardless of leap years. – Ryan Smith Sep 09 '15 at 14:03
  • 1
    The answer is incorrect also because strings such as a year `1234` with 4 characters will be considered incorrectly a date. This will work: https://plnkr.co/edit/7RZAcC?p=preview – diegosasw May 11 '16 at 00:07
  • @RyanSmith you're right but for this kind of validation better use something else than regex like [momentjs](http://momentjs.com/) – Édouard Lopez Oct 26 '16 at 09:22
  • 5
    @iberodev well `1234` is a valid date, also visit the link from my answer. – Édouard Lopez Oct 26 '16 at 09:26
  • This regexp detects `1234` as a date which is dangerous. Safer regexp: `/^\d{4}-(0[1-9]|1[0-2])-([12]\d|0[1-9]|3[01])([T\s](([01]\d|2[0-3])\:[0-5]\d|24\:00)(\:[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3])\:?([0-5]\d)?)?)?$/` – catamphetamine Dec 13 '16 at 23:32
  • 1
    @asdfasdfads `1234` is a valid date read the link: http://www.pelagodesign.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ – Édouard Lopez Dec 16 '16 at 10:03
  • @ÉdouardLopez what makes you think 1234 is not a valid date? The minimal ISO8601 date is a four-digit year. – Rob Grant Dec 22 '17 at 12:15
  • I'm adding a test case for `1234` so you can see it's matched by the regexp @RobertGrant – Édouard Lopez Dec 24 '17 at 10:49
  • 1
    Sorry, I addressed the wrong person with that comment, clearly! Apologies @ÉdouardLopez – Rob Grant Jan 02 '18 at 09:48
  • It seems that there are many dubious escapes in there, e.g. `\17` – Gregory Pakosz Jun 10 '21 at 12:25
  • @GregoryPakosz `\17` is a Numeric reference that Matches the results of capture group `#17`. You can get an [explanation on regexr](https://regexr.com/5vtrf) – Édouard Lopez Jun 14 '21 at 09:08
69

I found the RegExp that also tries to validate the date a bit overkill for me. I Just wanted to know if a string contains an ISO 8601 date string. I'll check if the date is actually valid after I have converted it to a Date object.

Here are 2 versions of the RegExp. This first checks if the string is a valid ISO 8601 date string. The other tests for a full date string including the hours/minutes/seconds (Commonly used in API's)

/**
 * RegExp to test a string for a ISO 8601 Date spec
 *  YYYY
 *  YYYY-MM
 *  YYYY-MM-DD
 *  YYYY-MM-DDThh:mmTZD
 *  YYYY-MM-DDThh:mm:ssTZD
 *  YYYY-MM-DDThh:mm:ss.sTZD
 * @see: https://www.w3.org/TR/NOTE-datetime
 * @type {RegExp}
 */
var ISO_8601 = /^\d{4}(-\d\d(-\d\d(T\d\d:\d\d(:\d\d)?(\.\d+)?(([+-]\d\d:\d\d)|Z)?)?)?)?$/i



/**
 * RegExp to test a string for a full ISO 8601 Date
 * Does not do any sort of date validation, only checks if the string is according to the ISO 8601 spec.
 *  YYYY-MM-DDThh:mm:ss
 *  YYYY-MM-DDThh:mm:ssTZD
 *  YYYY-MM-DDThh:mm:ss.sTZD
 * @see: https://www.w3.org/TR/NOTE-datetime
 * @type {RegExp}
 */
var ISO_8601_FULL = /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(([+-]\d\d:\d\d)|Z)?$/i


// Usage:

ISO_8601_FULL.test( "2016-05-24T15:54:14.876Z" )  // true
ISO_8601_FULL.test( "2002-12-31T23:00:00+01:00" ) // true
ISO_8601_FULL.test( "2016-02-01" )                // false
ISO_8601_FULL.test( "2016" )                      // false

ISO_8601.test( "2016-02-01" )                     // true
ISO_8601.test( "2016" )                           // true
ISO_8601.test( "2002-12-31T23:00:00+01:00" )      // true
SnailCrusher
  • 1,354
  • 12
  • 10
  • 3
    IMHO this answer covers more cases, like YYYY-MM-DDThh:mm:ss.sTZD formatted dates – ira Jan 19 '17 at 11:14
36

Don't quote the regex when specifying a regex in js. Forward slash is enough.

alert($('#datepicker').val());

if($('#datepicker').val().match(
    /(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})[+-](\d{2})\:(\d{2})/
)) {
    alert('ok');
} else {
    alert('not ok');
}​
Peter Kuhar
  • 606
  • 6
  • 6
  • Doh ! Thank you Peter. Shame on me, I already done this mistake by the past ^^ – TwystO Oct 06 '12 at 02:55
  • Also don't use \d. For one 9999-99-99:99:99:99 shouldn't match the standard. Besides that, \d does more than you'd usually think... http://stackoverflow.com/a/6479605/105484 – nategood Jun 08 '13 at 13:45
  • 2
    Below is my version: it is stricter than the ISO as it forces to have date, time(hh:mm:ss) and time zone. The only optional part is millisecond. (\d{4})-(0[1-9]|1[0-2]|[1-9])-(\3([12]\d|0[1-9]|3[01])|[1-9])[tT\s]([01]\d|2[0-3])\:(([0-5]\d)|\d)\:(([0-5]\d)|\d)([\.,]\d+)?([zZ]|([\+-])([01]\d|2[0-3]|\d):(([0-5]\d)|\d))$ – Reza Aug 28 '14 at 07:34
  • For me this regex worked: ^(\d{4})\-(\d{2})\-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})$ – Roboblob Feb 03 '16 at 08:55
  • 7
    Z suffix not supported – Onur Gazioğlu Jun 05 '17 at 12:38
15

JavaScript date.toISOString() regex

This only attempts to solve the basic pattern of 2017-06-17T00:00:00.000Z that you expect from Javascript doing it.

const isoPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;

One of the most annoying things about JSON is one cannot simply pass a date through and expect it to convert properly. Since most people use JavaScript, this is probably practical.

Here's a demo snippet if you have to pass to mongo and need to convert.

if (isoPattern.test(json.startDate))
  json.startDate = new Date(json.startDate);

I argue this is a better approach as you can be assured the date will parse, then you can check desired range, all being pretty straight forward and easy to maintain as regex is great but to a point.

King Friday
  • 25,132
  • 12
  • 90
  • 84
1

How about only testing if you can create a Date object of the string, if that is the purpose of the test?

new Date("2016-05-24T15:54:14.876Z").toString() === 'Invalid Date' // false
new Date("Invalid date").toString() === 'Invalid Date' // true
patriques
  • 5,057
  • 8
  • 38
  • 48
  • I like this approach but if the argument passed to 'Date' is null the result appears to be a valid date. If the argument is spaces or undefined the results are what you would expect but not if the it's null so just need to test for that . – glaucon Oct 10 '19 at 01:40
  • 1
    That seems to work nicely, until you get to the 31st of February... – HelpfulPanda May 26 '20 at 13:18
  • 2
    `new Date('1')` is also a valid date, so is `new Date('100000')` – CodeAndCats Jul 03 '20 at 06:18
0

To add to all these good answers, I found this one to be working quite good for just ISO dates (no time)

(?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:30))|(?:(?:0[13578]|1[02])-31))

(v = pass x = does not pass)

2016-12-30 v
2016-13-31 x
2016-01-32 x
2016-02-29 v
2016-02-30 x
2017-02-29 v -> that's a false positive
1889-01-01 x -> you can add accepted centuries in the list: (?:18|19|20)
2099-01-01 v
Sean Bright
  • 118,630
  • 17
  • 138
  • 146
stallingOne
  • 3,633
  • 3
  • 41
  • 63