Repository: dateutil/dateutil (Python)

Issue:
dateutil.parse.parse() rejects bytearray as input
```
>>> import dateutil.parser
>>> dateutil.parser.parse(b'17 Jan 2017')
datetime.datetime(2017, 1, 17, 0, 0)
>>> dateutil.parser.parse(bytearray(b'17 Jan 2017'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.5/site-packages/dateutil/parser.py", line 1168, in parse
    return DEFAULTPARSER.parse(timestr, **kwargs)
  File "/usr/lib/python3.5/site-packages/dateutil/parser.py", line 556, in parse
    res, skipped_tokens = self._parse(timestr, **kwargs)
  File "/usr/lib/python3.5/site-packages/dateutil/parser.py", line 675, in _parse
    l = _timelex.split(timestr)         # Splits the timestr into tokens
  File "/usr/lib/python3.5/site-packages/dateutil/parser.py", line 192, in split
    return list(cls(s))
  File "/usr/lib/python3.5/site-packages/dateutil/parser.py", line 61, in __init__
    '{itype}'.format(itype=instream.__class__.__name__))
TypeError: Parser must be a string or character stream, not bytearray
```

Any function which can operate on a `bytes` ought to be able to operate on a `bytearray`---unless something has specifically been done to prevent that, as in `dateutil.parser.parse()`. When `bytes` works, it's unexpected that `bytearray` would fail. It would be better to accept `bytearray`s as well.


Hints:
A one line change to `datetime.parser._timelex.__init__()` would fix this:
```
if isinstance(instream, (binary_type, bytearray)):
```
instead of
```
if isinstance(instream, binary_type):
```
However, letting duck typing work would be even better:
```
        if not hasattr(instream, 'read'):
            if hasattr(instream, 'decode'):
                instream = instream.decode()
            
            if isinstance(instream, text_type):
                instream = StringIO(instream)

            if not hasattr(instream, 'read'):
                raise TypeError('Parser must be a string or character stream, not '
                                '{itype}'.format(itype=instream.__class__.__name__))
```
I wouldn't say that support for `bytesarray` is particularly important here, but I am generally in favor of duck typing. I think that when we did the mypy typing for `typeshed`, we decided that the supported inputs would be `bytes`, `str` and "anything with a `.read()`".

Given that we're already calling `decode()`, I think probably replacing `bytes` with "anything with a `decode()` that returns either `str` or something with a `read()`" is reasonable. enough. I suspect the main problem with this is that it may cause problems if people provide `bytes` and `bytesarray`s encoded in something other than UTF-8, but that's already a problem (and one that's trivially solved by the fact that the first-class support is actually for `str` objects anyway), so I don't think it should be a blocker.

If you (or anyone coming across this issue) would like to make a PR, I'd go with the duck typed version, plus add a test where a `bytesarray` is parsed. Otherwise I'll do it eventually. Thanks for the report and taking the time to work out a solution, @uckelman 
The duck typing solution is in PR #418, along with two tests. Thanks for the quick reply.

Patch:
diff --git a/dateutil/parser/_parser.py b/dateutil/parser/_parser.py
--- a/dateutil/parser/_parser.py
+++ b/dateutil/parser/_parser.py
@@ -55,13 +55,18 @@ class _timelex(object):
     _split_decimal = re.compile("([.,])")
 
     def __init__(self, instream):
-        if isinstance(instream, binary_type):
-            instream = instream.decode()
+        if six.PY2:
+            # In Python 2, we can't duck type properly because unicode has
+            # a 'decode' function, and we'd be double-decoding
+            if isinstance(instream, (binary_type, bytearray)):
+                instream = instream.decode()
+        else:
+            if getattr(instream, 'decode', None) is not None:
+                instream = instream.decode()
 
         if isinstance(instream, text_type):
             instream = StringIO(instream)
-
-        if getattr(instream, 'read', None) is None:
+        elif getattr(instream, 'read', None) is None:
             raise TypeError('Parser must be a string or character stream, not '
                             '{itype}'.format(itype=instream.__class__.__name__))
 


Test patch:
diff --git a/dateutil/test/test_parser.py b/dateutil/test/test_parser.py
--- a/dateutil/test/test_parser.py
+++ b/dateutil/test/test_parser.py
@@ -102,6 +102,14 @@ def testParseStr(self):
         self.assertEqual(parse(self.str_str),
                          parse(self.uni_str))
 
+    def testParseBytes(self):
+        self.assertEqual(parse(b'2014 January 19'), datetime(2014, 1, 19))
+
+    def testParseBytearray(self):
+        # GH #417
+        self.assertEqual(parse(bytearray(b'2014 January 19')),
+                         datetime(2014, 1, 19))
+
     def testParserParseStr(self):
         from dateutil.parser import parser