<p>I'm trying to write a regular expression that validates a date.  The regex needs to match the following</p>

<ul>
<li>M/D/YYYY</li>
<li>MM/DD/YYYY</li>
<li>Single digit months can start with a leading zero (eg: 03/12/2008)</li>
<li>Single digit days can start with a leading zero (eg: 3/02/2008)</li>
<li>CANNOT include February 30 or February 31 (eg: 2/31/2008)</li>
</ul>

<p>So far I have </p>

<pre><code>^(([1-9]|1[012])[-/.]([1-9]|[12][0-9]|3[01])[-/.](19|20)\d\d)|((1[012]|0[1-9])(3[01]|2\d|1\d|0[1-9])(19|20)\d\d)|((1[012]|0[1-9])[-/.](3[01]|2\d|1\d|0[1-9])[-/.](19|20)\d\d)$
</code></pre>

<p>This matches properly EXCEPT it still includes 2/30/2008 &amp; 2/31/2008.</p>

<p>Does anyone have a better suggestion?</p>

<p><strong>Edit:</strong> I found <a href="http://regexlib.com/REDetails.aspx?regexp_id=112" rel="noreferrer">the answer</a> on RegExLib</p>

<pre><code>^((((0[13578])|([13578])|(1[02]))[\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\/](([1-9])|([0-2][0-9]))))[\/]\d{4}$|^\d{4}$
</code></pre>

<p>It matches all valid months that follow the MM/DD/YYYY format.</p>

<p>Thanks everyone for the help.</p>

[{'answer_id': 51227, 'author': 'Wedge', 'author_id': 332, 'author_profile': 'https://Stackoverflow.com/users/332', 'pm_score': 2, 'selected': False, 'text': "<p>Sounds like you're overextending regex for this purpose. What I would do is use a regex to match a few date formats and then use a separate function to validate the values of the date fields so extracted.</p>\n"}, {'answer_id': 51229, 'author': 'DevelopingChris', 'author_id': 1220, 'author_profile': 'https://Stackoverflow.com/users/1220', 'pm_score': 1, 'selected': False, 'text': '<p>Regex was not meant to validate number ranges(this number must be from 1 to 5 when the number preceding it happens to be a 2 and the number preceding that happens to be below 6).\nJust look for the pattern of placement of numbers in regex. If you need to validate is qualities of a date, put it in a date object js/c#/vb, and interogate the numbers there.</p>\n'}, {'answer_id': 51231, 'author': 'Chris Conway', 'author_id': 1412, 'author_profile': 'https://Stackoverflow.com/users/1412', 'pm_score': 8, 'selected': True, 'text': "<p>This is not an appropriate use of regular expressions. You'd be better off using</p>\n\n<pre><code>[0-9]{2}/[0-9]{2}/[0-9]{4}\n</code></pre>\n\n<p>and then checking ranges in a higher-level language.</p>\n"}, {'answer_id': 51235, 'author': 'Vinko Vrsalovic', 'author_id': 5190, 'author_profile': 'https://Stackoverflow.com/users/5190', 'pm_score': 1, 'selected': False, 'text': '<p>I know this does not answer your question, but why don\'t you use a date handling routine to check if it\'s a valid date? Even if you modify the regexp with a negative lookahead assertion like (?!31/0?2) (ie, do not match 31/2 or 31/02) you\'ll still have the problem of accepting 29 02 on non leap years and about a single separator date format. </p>\n\n<p>The problem is not easy if you want to really validate a date, check this <a href="http://discuss.joelonsoftware.com/default.asp?joel.3.229443.10" rel="nofollow noreferrer">forum thread</a>. </p>\n\n<p>For an example or a better way, in C#, check <a href="http://www.codeguru.com/columns/dotnettips/article.php/c7547" rel="nofollow noreferrer">this link</a></p>\n\n<p>If you are using another platform/language, let us know</p>\n'}, {'answer_id': 51236, 'author': 'Chris Conway', 'author_id': 1412, 'author_profile': 'https://Stackoverflow.com/users/1412', 'pm_score': 0, 'selected': False, 'text': "<p>If you're going to insist on doing this with a regular expression, I'd recommend something like:</p>\n\n<pre><code>( (0?1|0?3| &lt;...&gt; |10|11|12) / (0?1| &lt;...&gt; |30|31) |\n  0?2 / (0?1| &lt;...&gt; |28|29) ) \n/ (19|20)[0-9]{2}\n</code></pre>\n\n<p>This <em>might</em> make it possible to read and understand.</p>\n"}, {'answer_id': 60869, 'author': 'Brad Gilbert', 'author_id': 1337, 'author_profile': 'https://Stackoverflow.com/users/1337', 'pm_score': 2, 'selected': False, 'text': '<h2>Perl expanded version</h2>\n<p>Note use of <code>/x</code> modifier.</p>\n<pre><code>/^(\n      (\n        ( # 31 day months\n            (0[13578])\n          | ([13578])\n          | (1[02])\n        )\n        [\\/]\n        (\n            ([1-9])\n          | ([0-2][0-9])\n          | (3[01])\n        )\n      )\n    | (\n        ( # 30 day months\n            (0[469])\n          | ([469])\n          | (11)\n        )\n        [\\/]\n        (\n            ([1-9])\n          | ([0-2][0-9])\n          | (30)\n        )\n      )\n    | ( # 29 day month (Feb)\n        (2|02)\n        [\\/]\n        (\n            ([1-9])\n          | ([0-2][0-9])\n        )\n      )\n    )\n    [\\/]\n    # year\n    \\d{4}$\n  \n  | ^\\d{4}$ # year only\n/x\n</code></pre>\n<p>Original</p>\n<pre><code>^((((0[13578])|([13578])|(1[02]))[\\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\\/](([1-9])|([0-2][0-9]))))[\\/]\\d{4}$|^\\d{4}$\n</code></pre>\n'}, {'answer_id': 60890, 'author': 'Brad Gilbert', 'author_id': 1337, 'author_profile': 'https://Stackoverflow.com/users/1337', 'pm_score': 4, 'selected': False, 'text': '<h2>Maintainable Perl 5.10 version</h2>\n<pre><code>/\n  (?:\n      (?&lt;month&gt; (?&amp;mon_29)) [\\/] (?&lt;day&gt;(?&amp;day_29))\n    | (?&lt;month&gt; (?&amp;mon_30)) [\\/] (?&lt;day&gt;(?&amp;day_30))\n    | (?&lt;month&gt; (?&amp;mon_31)) [\\/] (?&lt;day&gt;(?&amp;day_31))\n  )\n  [\\/]\n  (?&lt;year&gt; [0-9]{4})\n  \n  (?(DEFINE)\n    (?&lt;mon_29&gt; 0?2 )\n    (?&lt;mon_30&gt; 0?[469]   | (11) )\n    (?&lt;mon_31&gt; 0?[13578] | 1[02] )\n\n    (?&lt;day_29&gt; 0?[1-9] | [1-2]?[0-9] )\n    (?&lt;day_30&gt; 0?[1-9] | [1-2]?[0-9] | 30 )\n    (?&lt;day_31&gt; 0?[1-9] | [1-2]?[0-9] | 3[01] )\n  )\n/x\n</code></pre>\n<p>You can retrieve the elements by name in this version.</p>\n<pre><code>say &quot;Month=$+{month} Day=$+{day} Year=$+{year}&quot;;\n</code></pre>\n<p>( No attempt has been made to restrict the values for the year. )</p>\n'}, {'answer_id': 60899, 'author': 'Brad Gilbert', 'author_id': 1337, 'author_profile': 'https://Stackoverflow.com/users/1337', 'pm_score': 1, 'selected': False, 'text': '<h1>Perl 6 version</h1>\n\n\n\n<pre class="lang-perl6 prettyprint-override"><code>rx{\n  ^\n\n  $&lt;month&gt; = (\\d ** 1..2)\n  { $&lt;month&gt; &lt;= 12 or fail }\n\n  \'/\'\n\n  $&lt;day&gt; = (\\d ** 1..2)\n  {\n    given( +$&lt;month&gt; ){\n      when 1|3|5|7|8|10|12 {\n        $&lt;day&gt; &lt;= 31 or fail\n      }\n      when 4|6|9|11 {\n        $&lt;day&gt; &lt;= 30 or fail\n      }\n      when 2 {\n        $&lt;day&gt; &lt;= 29 or fail\n      }\n      default { fail }\n    }\n  }\n\n  \'/\'\n\n  $&lt;year&gt; = (\\d ** 4)\n\n  $\n}\n</code></pre>\n\n<p>After you use this to check the input the values are available in <code>$/</code> or individually as <code>$&lt;month&gt;</code>, <code>$&lt;day&gt;</code>, <code>$&lt;year&gt;</code>. ( those are just syntax for accessing values in <code>$/</code> )</p>\n\n<p>No attempt has been made to check the year, or that it doesn\'t match the 29th of Feburary on non leap years.</p>\n'}, {'answer_id': 221748, 'author': 'Humpton', 'author_id': 1444, 'author_profile': 'https://Stackoverflow.com/users/1444', 'pm_score': -1, 'selected': False, 'text': "<p>A slightly different approach that may or may not be useful for you.</p>\n\n<p>I'm in php.</p>\n\n<p>The project this relates to will never have a date prior to the 1st of January 2008.  So, I take the 'date' inputed and use strtotime().  If the answer is >= 1199167200 then I have a date that is useful to me.  If something that doesn't look like a date is entered -1 is returned.  If null is entered it does return today's date number so you do need a check for a non-null entry first.</p>\n\n<p>Works for my situation, perhaps yours too?  </p>\n"}, {'answer_id': 8768241, 'author': 'Varun Achar', 'author_id': 652895, 'author_profile': 'https://Stackoverflow.com/users/652895', 'pm_score': 6, 'selected': False, 'text': '<p>Here is the Reg ex that matches all valid dates including leap years. Formats accepted mm/dd/yyyy or mm-dd-yyyy or mm.dd.yyyy format</p>\n\n<p><code>^(?:(?:(?:0?[13578]|1[02])(\\/|-|\\.)31)\\1|(?:(?:0?[1,3-9]|1[0-2])(\\/|-|\\.)(?:29|30)\\2))(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$|^(?:0?2(\\/|-|\\.)29\\3(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\\/|-|\\.)(?:0?[1-9]|1\\d|2[0-8])\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$</code></p>\n\n<p><em>courtesy <a href="http://www.advancedqtp.com/forums/index.php?topic=4394.0">Asiq Ahamed</a></em></p>\n'}, {'answer_id': 8949462, 'author': 'chuck akers', 'author_id': 1148562, 'author_profile': 'https://Stackoverflow.com/users/1148562', 'pm_score': 2, 'selected': False, 'text': "<p>if you didn't get those above suggestions working, I use this, as it gets any date I ran this expression through 50 links, and it got all the dates on each page. </p>\n\n<pre><code>^20\\d\\d-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(0[1-9]|[1-2][0-9]|3[01])$ \n</code></pre>\n"}, {'answer_id': 13533903, 'author': 'ALinnD', 'author_id': 1848216, 'author_profile': 'https://Stackoverflow.com/users/1848216', 'pm_score': 2, 'selected': False, 'text': "<pre><code>    var dtRegex = new RegExp(/[1-9\\-]{4}[0-9\\-]{2}[0-9\\-]{2}/);\n    if(dtRegex.test(date) == true){\n        var evalDate = date.split('-');\n        if(evalDate[0] != '0000' &amp;&amp; evalDate[1] != '00' &amp;&amp; evalDate[2] != '00'){\n            return true;\n        }\n    }\n</code></pre>\n"}, {'answer_id': 15968019, 'author': 'Okipa', 'author_id': 2273711, 'author_profile': 'https://Stackoverflow.com/users/2273711', 'pm_score': 3, 'selected': False, 'text': '<p>To control a date validity under the following format :</p>\n\n<blockquote>\n  <p>YYYY/MM/DD or YYYY-MM-DD</p>\n</blockquote>\n\n<p>I would recommand you tu use the following regular expression :</p>\n\n<pre><code>(((19|20)([2468][048]|[13579][26]|0[48])|2000)[/-]02[/-]29|((19|20)[0-9]{2}[/-](0[4678]|1[02])[/-](0[1-9]|[12][0-9]|30)|(19|20)[0-9]{2}[/-](0[1359]|11)[/-](0[1-9]|[12][0-9]|3[01])|(19|20)[0-9]{2}[/-]02[/-](0[1-9]|1[0-9]|2[0-8])))\n</code></pre>\n\n<p>Matches     </p>\n\n<blockquote>\n  <p>2016-02-29 | 2012-04-30 | 2019/09/31</p>\n</blockquote>\n\n<p>Non-Matches </p>\n\n<blockquote>\n  <p>2016-02-30 | 2012-04-31 | 2019/09/35</p>\n</blockquote>\n\n<p>You can customise it if you wants to allow only \'/\' or \'-\' separators.\nThis RegEx strictly controls the validity of the date and verify 28,30 and 31 days months, even leap years with 29/02 month.</p>\n\n<p>Try it, it works very well and prevent your code from lot of bugs !</p>\n\n<p>FYI : I made a variant for the SQL datetime. You\'ll find it there (look for my name) : <a href="https://stackoverflow.com/questions/1057716/regular-expression-to-validate-a-timestamp">Regular Expression to validate a timestamp</a></p>\n\n<p>Feedback are welcomed :)</p>\n'}, {'answer_id': 16285136, 'author': 'Enrique', 'author_id': 2333144, 'author_profile': 'https://Stackoverflow.com/users/2333144', 'pm_score': 2, 'selected': False, 'text': '<p>This regex validates dates between 01-01-2000 and 12-31-2099 with matching separators.</p>\n\n<pre><code>^(0[1-9]|1[012])([- /.])(0[1-9]|[12][0-9]|3[01])\\2(19|20)\\d\\d$\n</code></pre>\n'}, {'answer_id': 40309602, 'author': 'Bob', 'author_id': 177055, 'author_profile': 'https://Stackoverflow.com/users/177055', 'pm_score': 5, 'selected': False, 'text': "<p>I landed here because the title of this question is broad and I was looking for a regex that I could use to match on a specific date format (like the OP). But I then discovered, as many of the answers and comments have comprehensively highlighted, there are many pitfalls that make constructing an effective pattern very tricky when extracting dates that are mixed-in with poor quality or non-structured source data. </p>\n\n<p>In my exploration of the issues, I have come up with a system that enables you to build a regular expression by arranging together four simpler sub-expressions that match on the delimiter, and valid ranges for the year, month and day fields in the order you require. </p>\n\n<p>These are :-</p>\n\n<p><strong>Delimeters</strong></p>\n\n<pre><code>[^\\w\\d\\r\\n:] \n</code></pre>\n\n<p>This will match anything that is not a word character, digit character, carriage return, new line or colon. The colon has to be there to prevent matching on times that look like dates (see my test Data) </p>\n\n<p>You can optimise this part of the pattern to speed up matching, but this is a good foundation that detects most valid delimiters.  </p>\n\n<p>Note however; It will match a string with mixed delimiters like this 2/12-73 that may not actually be a valid date. </p>\n\n<p><strong>Year Values</strong></p>\n\n<pre><code>(\\d{4}|\\d{2})\n</code></pre>\n\n<p>This matches a group of two or 4 digits, in most cases this is acceptable, but if you're dealing with data from the years 0-999 or beyond 9999 you need to decide how to handle that because in most cases a 1, 3 or >4 digit year is garbage.</p>\n\n<p><strong>Month Values</strong></p>\n\n<pre><code>(0?[1-9]|1[0-2])\n</code></pre>\n\n<p>Matches any number between 1 and 12 with or without a leading zero - note: 0 and 00 is not matched.</p>\n\n<p><strong>Date Values</strong></p>\n\n<pre><code>(0?[1-9]|[12]\\d|30|31)\n</code></pre>\n\n<p>Matches any number between 1 and 31 with or without a leading zero - note: 0 and 00 is not matched.</p>\n\n<p><strong>This expression matches Date, Month, Year formatted dates</strong></p>\n\n<pre><code>(0?[1-9]|[12]\\d|30|31)[^\\w\\d\\r\\n:](0?[1-9]|1[0-2])[^\\w\\d\\r\\n:](\\d{4}|\\d{2})\n</code></pre>\n\n<p>But it will also match some of the Year, Month Date ones. It should also be bookended with the boundary operators to ensure the whole date string is selected and prevent valid sub-dates being extracted from data that is not well-formed i.e. without boundary tags 20/12/194 matches as 20/12/19 and 101/12/1974 matches as 01/12/1974 </p>\n\n<p>Compare the results of the next expression to the one above with the test data in the nonsense section (below)</p>\n\n<pre><code>\\b(0?[1-9]|[12]\\d|30|31)[^\\w\\d\\r\\n:](0?[1-9]|1[0-2])[^\\w\\d\\r\\n:](\\d{4}|\\d{2})\\b\n</code></pre>\n\n<p>There's no validation in this regex so a well-formed but invalid date such as 31/02/2001 would be matched. That is a data quality issue, and as others have said, your regex shouldn't need to validate the data. </p>\n\n<p>Because you (as a developer) can't guarantee the quality of the source data you do need to perform and handle additional validation in your code, if you try to match <strong>and</strong> validate the data in the RegEx it gets very messy and becomes difficult to support without <strong>very</strong> concise documentation.</p>\n\n<p>Garbage in, garbage out.</p>\n\n<p>Having said that, if you do have mixed formats where the date values vary, and you have to extract as much as you can; You can combine a couple of expressions together like so;</p>\n\n<p><strong>This (disastrous) expression matches DMY and YMD dates</strong> </p>\n\n<pre><code>(\\b(0?[1-9]|[12]\\d|30|31)[^\\w\\d\\r\\n:](0?[1-9]|1[0-2])[^\\w\\d\\r\\n:](\\d{4}|\\d{2})\\b)|(\\b(0?[1-9]|1[0-2])[^\\w\\d\\r\\n:](0?[1-9]|[12]\\d|30|31)[^\\w\\d\\r\\n:](\\d{4}|\\d{2})\\b)\n</code></pre>\n\n<p>BUT you won't be able to tell if dates like 6/9/1973 are the 6th of September or the 9th of June. I'm struggling to think of a scenario where that is not going to cause a problem somewhere down the line, it's bad practice and you shouldn't have to deal with it like that - find the data owner and hit them with the governance hammer.</p>\n\n<p>Finally, if you want to match a YYYYMMDD string with no delimiters you can take some of the uncertainty out and the expression looks like this</p>\n\n<pre><code>\\b(\\d{4})(0[1-9]|1[0-2])(0[1-9]|[12]\\d|30|31)\\b\n</code></pre>\n\n<p>But note again, it will match on well-formed but invalid values like 20010231 (31th Feb!) :)</p>\n\n<p><strong>Test data</strong></p>\n\n<p>In experimenting with the solutions in this thread I ended up with a test data set that includes a variety of valid and non-valid dates and some tricky situations where you may or may not want to match i.e. Times that could match as dates and dates on multiple lines. </p>\n\n<p>I hope this is useful to someone.</p>\n\n<pre><code>Valid Dates in various formats\n\nDay, month, year\n2/11/73\n02/11/1973\n2/1/73\n02/01/73\n31/1/1973\n02/1/1973\n31.1.2011\n31-1-2001\n29/2/1973\n29/02/1976 \n03/06/2010\n12/6/90\n\nmonth, day, year\n02/24/1975 \n06/19/66 \n03.31.1991\n2.29.2003\n02-29-55\n03-13-55\n03-13-1955\n12\\24\\1974\n12\\30\\1974\n1\\31\\1974\n03/31/2001\n01/21/2001\n12/13/2001\n\nMatch both DMY and MDY\n12/12/1978\n6/6/78\n06/6/1978\n6/06/1978\n\nusing whitespace as a delimiter\n\n13 11 2001\n11 13 2001\n11 13 01 \n13 11 01\n1 1 01\n1 1 2001\n\nYear Month Day order\n76/02/02\n1976/02/29\n1976/2/13\n76/09/31\n\nYYYYMMDD sortable format\n19741213\n19750101\n\nValid dates before Epoch\n12/1/10\n12/01/660\n12/01/00\n12/01/0000\n\nValid date after 2038\n\n01/01/2039\n01/01/39\n\nValid date beyond the year 9999\n\n01/01/10000\n\nDates with leading or trailing characters\n\n12/31/21/\n31/12/1921AD\n31/12/1921.10:55\n12/10/2016  8:26:00.39\nwfuwdf12/11/74iuhwf\nfwefew13/11/1974\n01/12/1974vdwdfwe\n01/01/99werwer\n12321301/01/99\n\nTimes that look like dates\n\n12:13:56\n13:12:01\n1:12:01PM\n1:12:01 AM\n\nDates that runs across two lines\n\n1/12/19\n74\n\n01/12/19\n74/13/1946\n\n31/12/20\n08:13\n\nInvalid, corrupted or nonsense dates\n\n0/1/2001\n1/0/2001\n00/01/2100\n01/0/2001\n0101/2001\n01/131/2001\n31/31/2001\n101/12/1974\n56/56/56\n00/00/0000\n0/0/1999\n12/01/0\n12/10/-100\n74/2/29\n12/32/45\n20/12/194\n\n2/12-73\n</code></pre>\n"}, {'answer_id': 69140085, 'author': 'Pankkaj', 'author_id': 5689392, 'author_profile': 'https://Stackoverflow.com/users/5689392', 'pm_score': 0, 'selected': False, 'text': '<p><code>/(([1-9]{1}|0[1-9]|1[0-2])\\/(0[1-9]|[1-9]{1}|[12]\\d|3[01])\\/[12]\\d{3})/</code></p>\n<p>This would validate for following -</p>\n<ul>\n<li>Single and 2 digit day with range from 1 to 31. Eg, 1, 01, 11, 31.</li>\n<li>Single and 2 digit month with range from 1 to 12. Eg. 1, 01, 12.</li>\n<li>4 digit year. Eg. 2021, 1980.</li>\n</ul>\n'}]
['https://Stackoverflow.com/questions/51224', 'https://Stackoverflow.com', 'https://Stackoverflow.com/users/3742/']