Repository: dateutil/dateutil (Python)

Issue:
Allow tzinfos to return None
Currently, if you the result of a `tzinfos` call *explicitly* returns `None`, that will throw an error:

```python
>>> from dateutil.parser import parse
>>> parse('2017-02-03 12:40 MGX', tzinfos={'MGX': None})
...
ValueError: Offset must be tzinfo subclass, tz string, or int offset.
>>> parse('2017-02-03 12:40 RDT', tzinfos={'XXX': None})
[...] UnknownTimezoneWarning: tzname RDT identified but not understood.  Pass `tzinfos`
argument in order to correctly return a timezone-aware datetime.  In a future version,
this raise an exception.
  category=UnknownTimezoneWarning)
datetime.datetime(2014, 2, 3, 2, 3)
```

The whole point of #540  was that implicitly silently failing was a bad behavior, but you should certainly be able to opt in to not caring about the time zone by returning `None`.

Note: As an aside, there's a typo I didn't notice there, it says "In a future version, this raise an exception", should be "this *will* raise an exception."


Hints:
@pganssle  Having a look at this. In  the second case shouldn't this still be the correct behaviour?
@ParseThis Yes, the second case is the correct behavior. I was just demonstrating that the behavior is only triggered when the `tzinfos` returns None, not whenever `parse` finds a timezone string and assigns `None`.

Patch:
diff --git a/dateutil/parser/_parser.py b/dateutil/parser/_parser.py
--- a/dateutil/parser/_parser.py
+++ b/dateutil/parser/_parser.py
@@ -1125,16 +1125,14 @@ def _build_tzinfo(self, tzinfos, tzname, tzoffset):
             tzdata = tzinfos(tzname, tzoffset)
         else:
             tzdata = tzinfos.get(tzname)
-
-        if isinstance(tzdata, datetime.tzinfo):
+        # handle case where tzinfo is paased an options that returns None
+        # eg tzinfos = {'BRST' : None}
+        if isinstance(tzdata, datetime.tzinfo) or tzdata is None:
             tzinfo = tzdata
         elif isinstance(tzdata, text_type):
             tzinfo = tz.tzstr(tzdata)
         elif isinstance(tzdata, integer_types):
             tzinfo = tz.tzoffset(tzname, tzdata)
-        else:
-            raise ValueError("Offset must be tzinfo subclass, "
-                             "tz string, or int offset.")
         return tzinfo
 
     def _build_tzaware(self, naive, res, tzinfos):
@@ -1170,7 +1168,7 @@ def _build_tzaware(self, naive, res, tzinfos):
             warnings.warn("tzname {tzname} identified but not understood.  "
                           "Pass `tzinfos` argument in order to correctly "
                           "return a timezone-aware datetime.  In a future "
-                          "version, this raise an "
+                          "version, this will raise an "
                           "exception.".format(tzname=res.tzname),
                           category=UnknownTimezoneWarning)
             aware = naive


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
@@ -170,7 +170,6 @@ def testDateCommandFormatWithLong(self):
                                    tzinfos={"BRST": long(-10800)}),
                              datetime(2003, 9, 25, 10, 36, 28,
                                       tzinfo=self.brsttz))
-
     def testDateCommandFormatIgnoreTz(self):
         self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003",
                                ignoretz=True),
@@ -748,6 +747,14 @@ def testUnspecifiedDayFallbackFebLeapYear(self):
         self.assertEqual(parse("Feb 2008", default=datetime(2010, 1, 31)),
                          datetime(2008, 2, 29))
 
+    def testTzinfoDictionaryCouldReturnNone(self):
+        self.assertEqual(parse('2017-02-03 12:40 BRST', tzinfos={"BRST": None}),
+                        datetime(2017, 2, 3, 12, 40))
+
+    def testTzinfosCallableCouldReturnNone(self):
+        self.assertEqual(parse('2017-02-03 12:40 BRST', tzinfos=lambda *args: None),
+                                    datetime(2017, 2, 3, 12, 40))
+
     def testErrorType01(self):
         self.assertRaises(ValueError,
                           parse, 'shouldfail')