Repository: dateutil/dateutil (Python)

Issue:
Isoparser misinterprets T24:00
When I originally wrote the ISO 8601 parser, I misinterpreted the standard. From [Wikipedia](https://en.wikipedia.org/wiki/ISO_8601#Times):

> Midnight is a special case and may be referred to as either "00:00" or "24:00". The notation "00:00" is used at the beginning of a calendar day and is the more frequently used. At the end of a day use "24:00". "2007-04-05T24:00" is the same instant as "2007-04-06T00:00" (see Combined date and time representations below).

But:

```python
>>> from dateutil.parser import isoparse
>>> isoparse("2007-04-05T24:00")
datetime.datetime(2007, 4, 5, 0, 0)
>>> isoparse("2007-04-06T00:00")
datetime.datetime(2007, 4, 6, 0, 0)
```

This is a bit tricky to handle because time and date parsing happen separately, so there's no obvious way to increment the date based on the value of the time until after it's parsed (at which point there is no difference between `00:00` and `24:00`).  I think the right solution is to allow [`_parse_isotime`](https://github.com/dateutil/dateutil/blob/master/dateutil/parser/isoparser.py#L318) to return `[24, 0, 0, 0, TZINFO]`, and handle that in [`parse_isotime`](https://github.com/dateutil/dateutil/blob/master/dateutil/parser/isoparser.py#L156) and [`parse_isodatetime`](https://github.com/dateutil/dateutil/blob/master/dateutil/parser/isoparser.py#L58) accordingly.

It's not clear to me whether the spec allows a bare time representation of `"24:00"` or if 24:00 is only valid in the context of a `datetime`. We should do whatever the spec says I think.


Hints:
May I try this one?

Patch:
diff --git a/dateutil/parser/isoparser.py b/dateutil/parser/isoparser.py
--- a/dateutil/parser/isoparser.py
+++ b/dateutil/parser/isoparser.py
@@ -139,6 +139,10 @@ def isoparse(self, dt_str):
             else:
                 raise ValueError('String contains unknown ISO components')
 
+        if len(components) > 3 and components[3] == 24:
+            components[3] = 0
+            return datetime(*components) + timedelta(days=1)
+
         return datetime(*components)
 
     @_takes_ascii
@@ -169,6 +173,9 @@ def parse_isotime(self, timestr):
         :return:
             Returns a :class:`datetime.time` object
         """
+        components = self._parse_isotime(timestr)
+        if components[0] == 24:
+            components[0] = 0
         return time(*self._parse_isotime(timestr))
 
     @_takes_ascii
@@ -368,7 +375,6 @@ def _parse_isotime(self, timestr):
             # Standard supports 00:00 and 24:00 as representations of midnight
             if any(component != 0 for component in components[1:4]):
                 raise ValueError('Hour may only be 24 at 24:00:00.000')
-            components[0] = 0
 
         return components
 


Test patch:
diff --git a/dateutil/test/test_isoparser.py b/dateutil/test/test_isoparser.py
--- a/dateutil/test/test_isoparser.py
+++ b/dateutil/test/test_isoparser.py
@@ -140,15 +140,15 @@ def test_full_tzoffsets(tzoffset):
 
 @pytest.mark.parametrize('dt_str', [
     '2014-04-11T00',
-    '2014-04-11T24',
+    '2014-04-10T24',
     '2014-04-11T00:00',
-    '2014-04-11T24:00',
+    '2014-04-10T24:00',
     '2014-04-11T00:00:00',
-    '2014-04-11T24:00:00',
+    '2014-04-10T24:00:00',
     '2014-04-11T00:00:00.000',
-    '2014-04-11T24:00:00.000',
+    '2014-04-10T24:00:00.000',
     '2014-04-11T00:00:00.000000',
-    '2014-04-11T24:00:00.000000']
+    '2014-04-10T24:00:00.000000']
 )
 def test_datetime_midnight(dt_str):
     assert isoparse(dt_str) == datetime(2014, 4, 11, 0, 0, 0, 0)