From 0adb544eeb4964b7cef51f58ad29fea25746ca36 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 20 Mar 2019 09:22:44 -0400 Subject: [PATCH 1/6] Combine and expand tests for tzinfos inputs The `tzinfos` argument allows you to add or override the mappings between time zone strings and tzinfo objects, this PR groups the existing tests for this argument together in a class, and expands on it to a few more cases. --- dateutil/test/test_parser.py | 48 ++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/dateutil/test/test_parser.py b/dateutil/test/test_parser.py index 7dc86751d..38e82ad53 100644 --- a/dateutil/test/test_parser.py +++ b/dateutil/test/test_parser.py @@ -347,6 +347,46 @@ def test_parse_bytearray(self): assert res == expected +class TestTzinfoInputTypes(object): + def test_tzinfo_dict_could_return_none(self): + dstr = "2017-02-03 12:40 BRST" + result = parse(dstr, tzinfos={"BRST": None}) + expected = datetime(2017, 2, 3, 12, 40) + assert result == expected + + def test_tzinfos_callable_could_return_none(self): + dstr = "2017-02-03 12:40 BRST" + result = parse(dstr, tzinfos=lambda *args: None) + expected = datetime(2017, 2, 3, 12, 40) + assert result == expected + + def test_valid_tzinfo_tzinfo_input(self): + dstr = "2014 January 19 09:00 UTC" + tzinfos = {"UTC": tz.UTC} + expected = datetime(2014, 1, 19, 9, tzinfo=tz.UTC) + res = parse(dstr, tzinfos=tzinfos) + assert res == expected + assert res.tzinfo is expected.tzinfo + + def test_valid_tzinfo_unicode_input(self): + dstr = "2014 January 19 09:00 UTC" + tzinfos = {u"UTC": u"UTC+0"} + expected = datetime(2014, 1, 19, 9, tzinfo=tz.UTC) + res = parse(dstr, tzinfos=tzinfos) + assert res == expected + + def test_valid_tzinfo_callable_input(self): + dstr = "2014 January 19 09:00 UTC" + + def tzinfos(*args, **kwargs): + return u"UTC+0" + + expected = datetime(2014, 1, 19, 9, tzinfo=tz.tzstr("UTC+0")) + res = parse(dstr, tzinfos=tzinfos) + assert res == expected + assert res.tzinfo is expected.tzinfo + + class ParserTest(unittest.TestCase): @classmethod @@ -507,14 +547,6 @@ 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): with pytest.raises(ValueError): parse('shouldfail') From 2dbf00aea19e8e784b1c4590dbc5f46d713dacf2 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 20 Mar 2019 09:24:32 -0400 Subject: [PATCH 2/6] Add failing test for invalid tzinfos dictionary Currently, passing an argument to `tzinfos` that cannot be interpreted as a time zone will raise UnboundLocalError, but it should raise TypeError. --- dateutil/test/test_parser.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dateutil/test/test_parser.py b/dateutil/test/test_parser.py index 38e82ad53..35f86f6d1 100644 --- a/dateutil/test/test_parser.py +++ b/dateutil/test/test_parser.py @@ -360,6 +360,14 @@ def test_tzinfos_callable_could_return_none(self): expected = datetime(2017, 2, 3, 12, 40) assert result == expected + @pytest.mark.xfail(reason="Incorrect error raised with bad tzinfo") + def test_invalid_tzinfo_input(self): + dstr = "2014 January 19 09:00 UTC" + # Pass an absurd tzinfos object + tzinfos = {"UTC": ValueError} + with pytest.raises(TypeError): + parse(dstr, tzinfos=tzinfos) + def test_valid_tzinfo_tzinfo_input(self): dstr = "2014 January 19 09:00 UTC" tzinfos = {"UTC": tz.UTC} From d0404c685dadaaa0b8b4c1317aea23a3d21ed4f9 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 20 Mar 2019 09:10:59 -0400 Subject: [PATCH 3/6] Fix error condition when invalid tzinfos is passed This switches the error from UnboundLocalError to TypeError. --- dateutil/parser/_parser.py | 3 +++ dateutil/test/test_parser.py | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dateutil/parser/_parser.py b/dateutil/parser/_parser.py index b70e2ca20..2ed091150 100644 --- a/dateutil/parser/_parser.py +++ b/dateutil/parser/_parser.py @@ -1172,6 +1172,9 @@ def _build_tzinfo(self, tzinfos, tzname, tzoffset): tzinfo = tz.tzstr(tzdata) elif isinstance(tzdata, integer_types): tzinfo = tz.tzoffset(tzname, tzdata) + else: + raise TypeError("Offset must be tzinfo subclass, tz string, " + "or int offset.") return tzinfo def _build_tzaware(self, naive, res, tzinfos): diff --git a/dateutil/test/test_parser.py b/dateutil/test/test_parser.py index 35f86f6d1..4e43dd6a4 100644 --- a/dateutil/test/test_parser.py +++ b/dateutil/test/test_parser.py @@ -360,7 +360,6 @@ def test_tzinfos_callable_could_return_none(self): expected = datetime(2017, 2, 3, 12, 40) assert result == expected - @pytest.mark.xfail(reason="Incorrect error raised with bad tzinfo") def test_invalid_tzinfo_input(self): dstr = "2014 January 19 09:00 UTC" # Pass an absurd tzinfos object From e8a1b8abd996fc7372b176d7a172734f88e2dac4 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 20 Mar 2019 09:13:49 -0400 Subject: [PATCH 4/6] Add changelog for PR #891 --- changelog.d/891.bugfix.rst | 4 ++++ changelog.d/891.misc.rst | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 changelog.d/891.bugfix.rst create mode 100644 changelog.d/891.misc.rst diff --git a/changelog.d/891.bugfix.rst b/changelog.d/891.bugfix.rst new file mode 100644 index 000000000..a5ed17ae4 --- /dev/null +++ b/changelog.d/891.bugfix.rst @@ -0,0 +1,4 @@ +``parser.parse`` will now raise ``TypeError`` when ``tzinfos`` is passed a type +that cannot be interpreted as a time zone. Prior to this change, it would raise +an ``UnboundLocalError`` instead. +Patch by @jbrockmendel (gh pr #891) diff --git a/changelog.d/891.misc.rst b/changelog.d/891.misc.rst new file mode 100644 index 000000000..2e7a44dd0 --- /dev/null +++ b/changelog.d/891.misc.rst @@ -0,0 +1,2 @@ +Added tests for tzinfos input types. +Patch by @jbrockmendel (gh pr #891) From b8bb468e852df2afdd12f20b6130c491408c3dd7 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Tue, 19 Mar 2019 09:40:10 -0400 Subject: [PATCH 5/6] Use convenience function for testing tz object For all of these tests, the most important part of the test is ensuring that the correct tzinfo object was attached, so it makes sense to add a convenience function that asserts that they are identical *and* have the same tzinfo object. --- dateutil/test/test_parser.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/dateutil/test/test_parser.py b/dateutil/test/test_parser.py index 4e43dd6a4..ccdfe87a3 100644 --- a/dateutil/test/test_parser.py +++ b/dateutil/test/test_parser.py @@ -348,17 +348,21 @@ def test_parse_bytearray(self): class TestTzinfoInputTypes(object): + def assert_equal_same_tz(self, dt1, dt2): + assert dt1 == dt2 + assert dt1.tzinfo is dt2.tzinfo + def test_tzinfo_dict_could_return_none(self): dstr = "2017-02-03 12:40 BRST" result = parse(dstr, tzinfos={"BRST": None}) expected = datetime(2017, 2, 3, 12, 40) - assert result == expected + self.assert_equal_same_tz(result, expected) def test_tzinfos_callable_could_return_none(self): dstr = "2017-02-03 12:40 BRST" result = parse(dstr, tzinfos=lambda *args: None) expected = datetime(2017, 2, 3, 12, 40) - assert result == expected + self.assert_equal_same_tz(result, expected) def test_invalid_tzinfo_input(self): dstr = "2014 January 19 09:00 UTC" @@ -372,15 +376,14 @@ def test_valid_tzinfo_tzinfo_input(self): tzinfos = {"UTC": tz.UTC} expected = datetime(2014, 1, 19, 9, tzinfo=tz.UTC) res = parse(dstr, tzinfos=tzinfos) - assert res == expected - assert res.tzinfo is expected.tzinfo + self.assert_equal_same_tz(res, expected) def test_valid_tzinfo_unicode_input(self): dstr = "2014 January 19 09:00 UTC" tzinfos = {u"UTC": u"UTC+0"} - expected = datetime(2014, 1, 19, 9, tzinfo=tz.UTC) + expected = datetime(2014, 1, 19, 9, tzinfo=tz.tzstr("UTC+0")) res = parse(dstr, tzinfos=tzinfos) - assert res == expected + self.assert_equal_same_tz(res, expected) def test_valid_tzinfo_callable_input(self): dstr = "2014 January 19 09:00 UTC" @@ -390,8 +393,7 @@ def tzinfos(*args, **kwargs): expected = datetime(2014, 1, 19, 9, tzinfo=tz.tzstr("UTC+0")) res = parse(dstr, tzinfos=tzinfos) - assert res == expected - assert res.tzinfo is expected.tzinfo + self.assert_equal_same_tz(res, expected) class ParserTest(unittest.TestCase): From 3a7f61913748158e3c9964d69047f4c0a32d3a1e Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Wed, 20 Mar 2019 09:04:19 -0400 Subject: [PATCH 6/6] Add test for interpreting UTC as an integer offset Co-authored-by: Brock Mendel --- dateutil/test/test_parser.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dateutil/test/test_parser.py b/dateutil/test/test_parser.py index ccdfe87a3..bf46dcecd 100644 --- a/dateutil/test/test_parser.py +++ b/dateutil/test/test_parser.py @@ -395,6 +395,13 @@ def tzinfos(*args, **kwargs): res = parse(dstr, tzinfos=tzinfos) self.assert_equal_same_tz(res, expected) + def test_valid_tzinfo_int_input(self): + dstr = "2014 January 19 09:00 UTC" + tzinfos = {u"UTC": -28800} + expected = datetime(2014, 1, 19, 9, tzinfo=tz.tzoffset(u"UTC", -28800)) + res = parse(dstr, tzinfos=tzinfos) + self.assert_equal_same_tz(res, expected) + class ParserTest(unittest.TestCase):