From 22715e30d158e9c22da8fb8853c3913882a9d4b7 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 27 Feb 2019 10:43:17 -0800 Subject: [PATCH 1/5] Parametrize generic parser test cases These tests have uninformative names, so they have been gathered into three parametetrized tests. --- dateutil/test/test_parser.py | 150 ++++++++++++----------------------- 1 file changed, 50 insertions(+), 100 deletions(-) diff --git a/dateutil/test/test_parser.py b/dateutil/test/test_parser.py index 413fa3fb8..9f1b74ea1 100644 --- a/dateutil/test/test_parser.py +++ b/dateutil/test/test_parser.py @@ -165,6 +165,56 @@ def test_parser_default(parsable_text, expected_datetime, assertion_message): assert parse(parsable_text, default=datetime(2003, 9, 25)) == expected_datetime, assertion_message +@pytest.mark.parametrize('sep', ['-', '.', '/', ' ']) +def test_parse_dayfirst(sep): + expected = datetime(2003, 9, 10) + fmt = sep.join(['%d', '%m', '%Y']) + dstr = expected.strftime(fmt) + result = parse(dstr, dayfirst=True) + assert result == expected + + +@pytest.mark.parametrize('sep', ['-', '.', '/', ' ']) +def test_parse_yearfirst(sep): + expected = datetime(2010, 9, 3) + fmt = sep.join(['%Y', '%m', '%d']) + dstr = expected.strftime(fmt) + result = parse(dstr, yearfirst=True) + assert result == expected + + +@pytest.mark.parametrize('dstr,expected', [ + ("Thu Sep 25 10:36:28 BRST 2003", datetime(2003, 9, 25, 10, 36, 28)), + ("1996.07.10 AD at 15:08:56 PDT", datetime(1996, 7, 10, 15, 8, 56)), + ("Tuesday, April 12, 1952 AD 3:30:42pm PST", datetime(1952, 4, 12, 15, 30, 42)), # noqa:E501 + ("November 5, 1994, 8:15:30 am EST", datetime(1994, 11, 5, 8, 15, 30)), + ("1994-11-05T08:15:30-05:00", datetime(1994, 11, 5, 8, 15, 30)), + ("1994-11-05T08:15:30Z", datetime(1994, 11, 5, 8, 15, 30)), + ("1976-07-04T00:01:02Z", datetime(1976, 7, 4, 0, 1, 2)), + ("1986-07-05T08:15:30z", datetime(1986, 7, 5, 8, 15, 30)), + ("Tue Apr 4 00:22:12 PDT 1995", datetime(1995, 4, 4, 0, 22, 12)), +]) +def test_parse_ignoretz(dstr, expected): + result = parse(dstr, ignoretz=True) + assert result == expected + + +_brsttz = tzoffset("BRST", -10800) + + +@pytest.mark.parametrize('dstr,expected', [ + ("20030925T104941-0300", datetime(2003, 9, 25, 10, 49, 41, tzinfo=_brsttz)), + ("Thu, 25 Sep 2003 10:49:41 -0300", datetime(2003, 9, 25, 10, 49, 41, tzinfo=_brsttz)), # noqa:#501 + ("2003-09-25T10:49:41.5-03:00", datetime(2003, 9, 25, 10, 49, 41, 500000, tzinfo=_brsttz)), # noqa:#501 + ("2003-09-25T10:49:41-03:00", datetime(2003, 9, 25, 10, 49, 41, tzinfo=_brsttz)), # noqa:#501 + ("20030925T104941.5-0300", datetime(2003, 9, 25, 10, 49, 41, 500000, tzinfo=_brsttz)), # noqa:#501 +]) +def test_parse_with_tzoffset(dstr, expected): + # In these cases, we are _not_ passing a tzinfos arg + result = parse(dstr) + assert result == expected + + class TestFormat(object): def test_ybd(self): @@ -315,78 +365,16 @@ def testDateCommandFormatWithLong(self): 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), - datetime(2003, 9, 25, 10, 36, 28)) - - def testDateRCommandFormat(self): - self.assertEqual(parse("Thu, 25 Sep 2003 10:49:41 -0300"), - datetime(2003, 9, 25, 10, 49, 41, - tzinfo=self.brsttz)) - - def testISOFormat(self): - self.assertEqual(parse("2003-09-25T10:49:41.5-03:00"), - datetime(2003, 9, 25, 10, 49, 41, 500000, - tzinfo=self.brsttz)) - - def testISOFormatStrip1(self): - self.assertEqual(parse("2003-09-25T10:49:41-03:00"), - datetime(2003, 9, 25, 10, 49, 41, - tzinfo=self.brsttz)) - def testISOFormatStrip2(self): self.assertEqual(parse("2003-09-25T10:49:41+03:00"), datetime(2003, 9, 25, 10, 49, 41, tzinfo=tzoffset(None, 10800))) - def testISOStrippedFormat(self): - self.assertEqual(parse("20030925T104941.5-0300"), - datetime(2003, 9, 25, 10, 49, 41, 500000, - tzinfo=self.brsttz)) - - def testISOStrippedFormatStrip1(self): - self.assertEqual(parse("20030925T104941-0300"), - datetime(2003, 9, 25, 10, 49, 41, - tzinfo=self.brsttz)) - def testISOStrippedFormatStrip2(self): self.assertEqual(parse("20030925T104941+0300"), datetime(2003, 9, 25, 10, 49, 41, tzinfo=tzoffset(None, 10800))) - def testDateWithDash8(self): - self.assertEqual(parse("10-09-2003", dayfirst=True), - datetime(2003, 9, 10)) - - def testDateWithDash11(self): - self.assertEqual(parse("10-09-03", yearfirst=True), - datetime(2010, 9, 3)) - - def testDateWithDot8(self): - self.assertEqual(parse("10.09.2003", dayfirst=True), - datetime(2003, 9, 10)) - - def testDateWithDot11(self): - self.assertEqual(parse("10.09.03", yearfirst=True), - datetime(2010, 9, 3)) - - def testDateWithSlash8(self): - self.assertEqual(parse("10/09/2003", dayfirst=True), - datetime(2003, 9, 10)) - - def testDateWithSlash11(self): - self.assertEqual(parse("10/09/03", yearfirst=True), - datetime(2010, 9, 3)) - - def testDateWithSpace8(self): - self.assertEqual(parse("10 09 2003", dayfirst=True), - datetime(2003, 9, 10)) - - def testDateWithSpace11(self): - self.assertEqual(parse("10 09 03", yearfirst=True), - datetime(2010, 9, 3)) - def testAMPMNoHour(self): with pytest.raises(ValueError): parse("AM") @@ -449,44 +437,6 @@ def testFuzzyIgnoreAMPM(self): res = parse(s1, fuzzy=True) self.assertEqual(res, datetime(1945, 1, 29, 14, 45)) - def testRandomFormat2(self): - self.assertEqual(parse("1996.07.10 AD at 15:08:56 PDT", - ignoretz=True), - datetime(1996, 7, 10, 15, 8, 56)) - - def testRandomFormat4(self): - self.assertEqual(parse("Tuesday, April 12, 1952 AD 3:30:42pm PST", - ignoretz=True), - datetime(1952, 4, 12, 15, 30, 42)) - - def testRandomFormat5(self): - self.assertEqual(parse("November 5, 1994, 8:15:30 am EST", - ignoretz=True), - datetime(1994, 11, 5, 8, 15, 30)) - - def testRandomFormat6(self): - self.assertEqual(parse("1994-11-05T08:15:30-05:00", - ignoretz=True), - datetime(1994, 11, 5, 8, 15, 30)) - - def testRandomFormat7(self): - self.assertEqual(parse("1994-11-05T08:15:30Z", - ignoretz=True), - datetime(1994, 11, 5, 8, 15, 30)) - - def testRandomFormat17(self): - self.assertEqual(parse("1976-07-04T00:01:02Z", ignoretz=True), - datetime(1976, 7, 4, 0, 1, 2)) - - def testRandomFormat18(self): - self.assertEqual(parse("1986-07-05T08:15:30z", - ignoretz=True), - datetime(1986, 7, 5, 8, 15, 30)) - - def testRandomFormat20(self): - self.assertEqual(parse("Tue Apr 4 00:22:12 PDT 1995", ignoretz=True), - datetime(1995, 4, 4, 0, 22, 12)) - def testRandomFormat24(self): self.assertEqual(parse("0:00 PM, PST", default=self.default, ignoretz=True), From 5cd63ccc47c7667ef5cb4fabc4c91581077996a7 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 27 Feb 2019 10:44:01 -0800 Subject: [PATCH 2/5] Add tests for inverting strftime These test that various `strftime` formats are invertable by the parser, for the chosen date, they are all unambiguous. and independent of time. In the future, at least some of these can be moved into a property test that does not hard code the format or the date. --- dateutil/test/test_parser.py | 58 ++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/dateutil/test/test_parser.py b/dateutil/test/test_parser.py index 9f1b74ea1..002fd517a 100644 --- a/dateutil/test/test_parser.py +++ b/dateutil/test/test_parser.py @@ -186,7 +186,8 @@ def test_parse_yearfirst(sep): @pytest.mark.parametrize('dstr,expected', [ ("Thu Sep 25 10:36:28 BRST 2003", datetime(2003, 9, 25, 10, 36, 28)), ("1996.07.10 AD at 15:08:56 PDT", datetime(1996, 7, 10, 15, 8, 56)), - ("Tuesday, April 12, 1952 AD 3:30:42pm PST", datetime(1952, 4, 12, 15, 30, 42)), # noqa:E501 + ("Tuesday, April 12, 1952 AD 3:30:42pm PST", + datetime(1952, 4, 12, 15, 30, 42)), ("November 5, 1994, 8:15:30 am EST", datetime(1994, 11, 5, 8, 15, 30)), ("1994-11-05T08:15:30-05:00", datetime(1994, 11, 5, 8, 15, 30)), ("1994-11-05T08:15:30Z", datetime(1994, 11, 5, 8, 15, 30)), @@ -203,11 +204,16 @@ def test_parse_ignoretz(dstr, expected): @pytest.mark.parametrize('dstr,expected', [ - ("20030925T104941-0300", datetime(2003, 9, 25, 10, 49, 41, tzinfo=_brsttz)), - ("Thu, 25 Sep 2003 10:49:41 -0300", datetime(2003, 9, 25, 10, 49, 41, tzinfo=_brsttz)), # noqa:#501 - ("2003-09-25T10:49:41.5-03:00", datetime(2003, 9, 25, 10, 49, 41, 500000, tzinfo=_brsttz)), # noqa:#501 - ("2003-09-25T10:49:41-03:00", datetime(2003, 9, 25, 10, 49, 41, tzinfo=_brsttz)), # noqa:#501 - ("20030925T104941.5-0300", datetime(2003, 9, 25, 10, 49, 41, 500000, tzinfo=_brsttz)), # noqa:#501 + ("20030925T104941-0300", + datetime(2003, 9, 25, 10, 49, 41, tzinfo=_brsttz)), + ("Thu, 25 Sep 2003 10:49:41 -0300", + datetime(2003, 9, 25, 10, 49, 41, tzinfo=_brsttz)), + ("2003-09-25T10:49:41.5-03:00", + datetime(2003, 9, 25, 10, 49, 41, 500000, tzinfo=_brsttz)), + ("2003-09-25T10:49:41-03:00", + datetime(2003, 9, 25, 10, 49, 41, tzinfo=_brsttz)), + ("20030925T104941.5-0300", + datetime(2003, 9, 25, 10, 49, 41, 500000, tzinfo=_brsttz)), ]) def test_parse_with_tzoffset(dstr, expected): # In these cases, we are _not_ passing a tzinfos arg @@ -242,6 +248,46 @@ def test_ybd(self): res = parse(dstr) assert res == actual + # TODO: some redundancy with PARSER_TEST_CASES cases + @pytest.mark.parametrize("fmt,dstr", [ + ("%a %b %d %Y", "Thu Sep 25 2003"), + ("%b %d %Y", "Sep 25 2003"), + ("%Y-%m-%d", "2003-09-25"), + ("%Y%m%d", "20030925"), + ("%Y-%b-%d", "2003-Sep-25"), + ("%d-%b-%Y", "25-Sep-2003"), + ("%b-%d-%Y", "Sep-25-2003"), + ("%m-%d-%Y", "09-25-2003"), + ("%d-%m-%Y", "25-09-2003"), + ("%Y.%m.%d", "2003.09.25"), + ("%Y.%b.%d", "2003.Sep.25"), + ("%d.%b.%Y", "25.Sep.2003"), + ("%b.%d.%Y", "Sep.25.2003"), + ("%m.%d.%Y", "09.25.2003"), + ("%d.%m.%Y", "25.09.2003"), + ("%Y/%m/%d", "2003/09/25"), + ("%Y/%b/%d", "2003/Sep/25"), + ("%d/%b/%Y", "25/Sep/2003"), + ("%b/%d/%Y", "Sep/25/2003"), + ("%m/%d/%Y", "09/25/2003"), + ("%d/%m/%Y", "25/09/2003"), + ("%Y %m %d", "2003 09 25"), + ("%Y %b %d", "2003 Sep 25"), + ("%d %b %Y", "25 Sep 2003"), + ("%m %d %Y", "09 25 2003"), + ("%d %m %Y", "25 09 2003"), + ("%y %d %b", "03 25 Sep",), + ]) + def test_strftime_formats_2003Sep25(self, fmt, dstr): + expected = datetime(2003, 9, 25) + + # First check that the format strings behave as expected + # (not strictly necessary, but nice to have) + assert expected.strftime(fmt) == dstr + + res = parse(dstr) + assert res == expected + class TestInputTypes(object): def test_empty_string_invalid(self): From 7347e23842afc92ffd50cf498e7130e83bd5408f Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 27 Feb 2019 10:46:21 -0800 Subject: [PATCH 3/5] Collect TZEnvContext cases in a class This improves the organization of the test cases and also allows us to skip all tests requiring TZEnvContext at the class level. --- dateutil/test/test_parser.py | 75 +++++++++++++++++------------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/dateutil/test/test_parser.py b/dateutil/test/test_parser.py index 002fd517a..c3ebcabdb 100644 --- a/dateutil/test/test_parser.py +++ b/dateutil/test/test_parser.py @@ -813,45 +813,42 @@ def test_unambiguous_YYYYMM(self): assert res == expected -@pytest.mark.skipif(IS_WIN, reason='Windows does not use TZ var') -def test_parse_unambiguous_nonexistent_local(): - # When dates are specified "EST" even when they should be "EDT" in the - # local time zone, we should still assign the local time zone - with TZEnvContext('EST+5EDT,M3.2.0/2,M11.1.0/2'): - dt_exp = datetime(2011, 8, 1, 12, 30, tzinfo=tz.tzlocal()) - dt = parse('2011-08-01T12:30 EST') - - assert dt.tzname() == 'EDT' - assert dt == dt_exp - - -@pytest.mark.skipif(IS_WIN, reason='Windows does not use TZ var') -def test_tzlocal_in_gmt(): - # GH #318 - with TZEnvContext('GMT0BST,M3.5.0,M10.5.0'): - # This is an imaginary datetime in tz.tzlocal() but should still - # parse using the GMT-as-alias-for-UTC rule - dt = parse('2004-05-01T12:00 GMT') - dt_exp = datetime(2004, 5, 1, 12, tzinfo=tz.tzutc()) - - assert dt == dt_exp - - -@pytest.mark.skipif(IS_WIN, reason='Windows does not use TZ var') -def test_tzlocal_parse_fold(): - # One manifestion of GH #318 - with TZEnvContext('EST+5EDT,M3.2.0/2,M11.1.0/2'): - dt_exp = datetime(2011, 11, 6, 1, 30, tzinfo=tz.tzlocal()) - dt_exp = tz.enfold(dt_exp, fold=1) - dt = parse('2011-11-06T01:30 EST') - - # Because this is ambiguous, kuntil `tz.tzlocal() is tz.tzlocal()` - # we'll just check the attributes we care about rather than - # dt == dt_exp - assert dt.tzname() == dt_exp.tzname() - assert dt.replace(tzinfo=None) == dt_exp.replace(tzinfo=None) - assert getattr(dt, 'fold') == getattr(dt_exp, 'fold') - assert dt.astimezone(tz.tzutc()) == dt_exp.astimezone(tz.tzutc()) +@pytest.mark.skipif(IS_WIN, reason="Windows does not use TZ var") +class TestTZVar(object): + def test_parse_unambiguous_nonexistent_local(self): + # When dates are specified "EST" even when they should be "EDT" in the + # local time zone, we should still assign the local time zone + with TZEnvContext('EST+5EDT,M3.2.0/2,M11.1.0/2'): + dt_exp = datetime(2011, 8, 1, 12, 30, tzinfo=tz.tzlocal()) + dt = parse('2011-08-01T12:30 EST') + + assert dt.tzname() == 'EDT' + assert dt == dt_exp + + def test_tzlocal_in_gmt(self): + # GH #318 + with TZEnvContext('GMT0BST,M3.5.0,M10.5.0'): + # This is an imaginary datetime in tz.tzlocal() but should still + # parse using the GMT-as-alias-for-UTC rule + dt = parse('2004-05-01T12:00 GMT') + dt_exp = datetime(2004, 5, 1, 12, tzinfo=tz.tzutc()) + + assert dt == dt_exp + + def test_tzlocal_parse_fold(self): + # One manifestion of GH #318 + with TZEnvContext('EST+5EDT,M3.2.0/2,M11.1.0/2'): + dt_exp = datetime(2011, 11, 6, 1, 30, tzinfo=tz.tzlocal()) + dt_exp = tz.enfold(dt_exp, fold=1) + dt = parse('2011-11-06T01:30 EST') + + # Because this is ambiguous, kuntil `tz.tzlocal() is tz.tzlocal()` + # we'll just check the attributes we care about rather than + # dt == dt_exp + assert dt.tzname() == dt_exp.tzname() + assert dt.replace(tzinfo=None) == dt_exp.replace(tzinfo=None) + assert getattr(dt, 'fold') == getattr(dt_exp, 'fold') + assert dt.astimezone(tz.tzutc()) == dt_exp.astimezone(tz.tzutc()) def test_parse_tzinfos_fold(): From 6623110c4b728a3d1c2876ab347a23d026b0ac89 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 1 Mar 2019 08:30:27 -0800 Subject: [PATCH 4/5] Fix typo in comment --- dateutil/test/test_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dateutil/test/test_parser.py b/dateutil/test/test_parser.py index c3ebcabdb..7dc86751d 100644 --- a/dateutil/test/test_parser.py +++ b/dateutil/test/test_parser.py @@ -842,7 +842,7 @@ def test_tzlocal_parse_fold(self): dt_exp = tz.enfold(dt_exp, fold=1) dt = parse('2011-11-06T01:30 EST') - # Because this is ambiguous, kuntil `tz.tzlocal() is tz.tzlocal()` + # Because this is ambiguous, until `tz.tzlocal() is tz.tzlocal()` # we'll just check the attributes we care about rather than # dt == dt_exp assert dt.tzname() == dt_exp.tzname() From d0acb3f9c2ae76e033b77f96d404ec82b8bbb1a2 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 27 Feb 2019 10:53:36 -0800 Subject: [PATCH 5/5] Add changelog for PR #894 --- changelog.d/894.misc.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelog.d/894.misc.rst diff --git a/changelog.d/894.misc.rst b/changelog.d/894.misc.rst new file mode 100644 index 000000000..9faadabd2 --- /dev/null +++ b/changelog.d/894.misc.rst @@ -0,0 +1,2 @@ +Parametrized parser test cases. +Patch by @jbrockmendel (gh pr #894)