Repository: dateutil/dateutil (Python)

Issue:
Deprecate idiosyncratic tzstr format
Per [this question](https://stackoverflow.com/q/47874633/467366) on SO, unless we can find some sort of standard that matches up with `# GMT0BST,3,0,30,3600,10,0,26,7200[,3600]`, I think we can go ahead and deprecate it as a supported `tzstr` format (just in time now that @pablogsal has fixed it our support for it)!

I'm thinking we should

- [x] Add a `DeprecatedFormatWarning` on a successfully parsed string in this format (not a `DeprecationWarning`, I want people to actually see it)
- [x] Change all tests using this format so that they now check to make sure the warning is raised.
- [x] Remove this format from all tests where we're not *explicitly* testing our support for the format.
- [x] Remove any code examples or documentation related to this format, except possibly a `.. caution` note about the deprecation of the format.

I'm fairly confident that this won't cause major problems, since support for this format was inadvertently broken several releases back and we haven't heard anything.
  


Hints:

Patch:
diff --git a/dateutil/parser/_parser.py b/dateutil/parser/_parser.py
--- a/dateutil/parser/_parser.py
+++ b/dateutil/parser/_parser.py
@@ -44,7 +44,7 @@
 
 from decimal import Decimal
 
-import re
+from warnings import warn
 
 from .. import relativedelta
 from .. import tz
@@ -1419,6 +1419,14 @@ def parse(self, tzstr):
                         signal = 1
                     used_idxs.append(i)
                     res.dstoffset = (res.stdoffset + int(l[i]) * signal)
+
+                # This was a made-up format that is not in normal use
+                warn(('Parsed time zone "%s"' % tzstr) +
+                     'is in a non-standard dateutil-specific format, which ' +
+                     'is now deprecated; support for parsing this format ' +
+                     'will be removed in future versions. It is recommended ' +
+                     'that you switch to a standard format like the GNU ' +
+                     'TZ variable format.', tz.DeprecatedTzFormatWarning)
             elif (l.count(',') == 2 and l[i:].count('/') <= 2 and
                   not [y for x in l[i:] if x not in (',', '/', 'J', 'M',
                                                      '.', '-', ':')
diff --git a/dateutil/tz/__init__.py b/dateutil/tz/__init__.py
--- a/dateutil/tz/__init__.py
+++ b/dateutil/tz/__init__.py
@@ -8,4 +8,8 @@
 __all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
            "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz",
            "enfold", "datetime_ambiguous", "datetime_exists",
-           "UTC"]
+           "UTC", "DeprecatedTzFormatWarning"]
+
+
+class DeprecatedTzFormatWarning(Warning):
+    """Warning raised when time zones are parsed from deprecated formats."""
diff --git a/dateutil/tz/tz.py b/dateutil/tz/tz.py
--- a/dateutil/tz/tz.py
+++ b/dateutil/tz/tz.py
@@ -992,6 +992,18 @@ class tzstr(tzrange):
         ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the
         POSIX standard.
 
+    .. caution::
+
+        Prior to version 2.7.0, this function also supported time zones
+        in the format:
+
+            * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600``
+            * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600``
+
+        This format is non-standard and has been deprecated; this function
+        will raise a :class:`DeprecatedTZFormatWarning` until
+        support is removed in a future version.
+
     .. _`GNU C Library: TZ Variable`:
         https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
     """


Test patch:
diff --git a/dateutil/test/test_internals.py b/dateutil/test/test_internals.py
--- a/dateutil/test/test_internals.py
+++ b/dateutil/test/test_internals.py
@@ -13,6 +13,7 @@
 import pytest
 
 from dateutil.parser._parser import _ymd
+from dateutil import tz
 
 IS_PY32 = sys.version_info[0:2] == (3, 2)
 
@@ -80,3 +81,15 @@ def test_parser_parser_private_not_warns():
     with pytest.warns(None) as recorder:
         _parsetz('+05:00')
         assert len(recorder) == 0
+
+
+@pytest.mark.tzstr
+def test_tzstr_internal_timedeltas():
+    with pytest.warns(tz.DeprecatedTzFormatWarning):
+        tz1 = tz.tzstr("EST5EDT,5,4,0,7200,11,-3,0,7200")
+
+    with pytest.warns(tz.DeprecatedTzFormatWarning):
+        tz2 = tz.tzstr("EST5EDT,4,1,0,7200,10,-1,0,7200")
+
+    assert tz1._start_delta != tz2._start_delta
+    assert tz1._end_delta != tz2._end_delta
diff --git a/dateutil/test/test_tz.py b/dateutil/test/test_tz.py
--- a/dateutil/test/test_tz.py
+++ b/dateutil/test/test_tz.py
@@ -151,10 +151,19 @@
 END:VTIMEZONE
 """
 
+EST_TUPLE = ('EST', timedelta(hours=-5), timedelta(hours=0))
+EDT_TUPLE = ('EDT', timedelta(hours=-4), timedelta(hours=1))
+
+
 ###
-# Mix-ins
+# Helper functions
+def get_timezone_tuple(dt):
+    """Retrieve a (tzname, utcoffset, dst) tuple for a given DST"""
+    return dt.tzname(), dt.utcoffset(), dt.dst()
 
 
+###
+# Mix-ins
 class context_passthrough(object):
     def __init__(*args, **kwargs):
         pass
@@ -1222,106 +1231,6 @@ def gettz(self, tzname):
 
         return tz.tzstr(tzname_map[tzname])
 
-
-    def test_internal_timedeltas(self):
-        self.assertNotEqual(tz.tzstr("EST5EDT,5,4,0,7200,11,-3,0,7200")._start_delta,
-                            tz.tzstr("EST5EDT,4,1,0,7200,10,-1,0,7200")._start_delta)
-
-        self.assertNotEqual(tz.tzstr("EST5EDT,5,4,0,7200,11,-3,0,7200")._end_delta,
-                            tz.tzstr("EST5EDT,4,1,0,7200,10,-1,0,7200")._end_delta)
-
-    def testStrStart1(self):
-        self.assertEqual(datetime(2003, 4, 6, 1, 59,
-                                  tzinfo=tz.tzstr("EST5EDT")).tzname(), "EST")
-        self.assertEqual(datetime(2003, 4, 6, 2, 00,
-                                  tzinfo=tz.tzstr("EST5EDT")).tzname(), "EDT")
-
-    def testStrEnd1(self):
-        self.assertEqual(datetime(2003, 10, 26, 0, 59,
-                                  tzinfo=tz.tzstr("EST5EDT")).tzname(), "EDT")
-
-        end = tz.enfold(datetime(2003, 10, 26, 1, 00,
-                                 tzinfo=tz.tzstr("EST5EDT")), fold=1)
-        self.assertEqual(end.tzname(), "EST")
-
-    def testStrStart2(self):
-        s = "EST5EDT,4,0,6,7200,10,0,26,7200,3600"
-        self.assertEqual(datetime(2003, 4, 6, 1, 59,
-                                  tzinfo=tz.tzstr(s)).tzname(), "EST")
-        self.assertEqual(datetime(2003, 4, 6, 2, 00,
-                                  tzinfo=tz.tzstr(s)).tzname(), "EDT")
-
-    def testStrEnd2(self):
-        s = "EST5EDT,4,0,6,7200,10,0,26,7200,3600"
-        self.assertEqual(datetime(2003, 10, 26, 0, 59,
-                                  tzinfo=tz.tzstr(s)).tzname(), "EDT")
-
-        end = tz.enfold(datetime(2003, 10, 26, 1, 00,
-                                 tzinfo=tz.tzstr(s)), fold=1)
-        self.assertEqual(end.tzname(), "EST")
-
-    def testStrStart3(self):
-        s = "EST5EDT,4,1,0,7200,10,-1,0,7200,3600"
-        self.assertEqual(datetime(2003, 4, 6, 1, 59,
-                                  tzinfo=tz.tzstr(s)).tzname(), "EST")
-        self.assertEqual(datetime(2003, 4, 6, 2, 00,
-                                  tzinfo=tz.tzstr(s)).tzname(), "EDT")
-
-    def testStrEnd3(self):
-        s = "EST5EDT,4,1,0,7200,10,-1,0,7200,3600"
-        self.assertEqual(datetime(2003, 10, 26, 0, 59,
-                                  tzinfo=tz.tzstr(s)).tzname(), "EDT")
-
-        end = tz.enfold(datetime(2003, 10, 26, 1, 00,
-                                 tzinfo=tz.tzstr(s)), fold=1)
-        self.assertEqual(end.tzname(), "EST")
-
-    def testStrStart4(self):
-        s = "EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00"
-        self.assertEqual(datetime(2003, 4, 6, 1, 59,
-                                  tzinfo=tz.tzstr(s)).tzname(), "EST")
-        self.assertEqual(datetime(2003, 4, 6, 2, 00,
-                                  tzinfo=tz.tzstr(s)).tzname(), "EDT")
-
-    def testStrEnd4(self):
-        s = "EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00"
-        self.assertEqual(datetime(2003, 10, 26, 0, 59,
-                                  tzinfo=tz.tzstr(s)).tzname(), "EDT")
-        end = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tz.tzstr(s)),
-                        fold=1)
-        self.assertEqual(end.tzname(), "EST")
-
-    def testStrStart5(self):
-        s = "EST5EDT4,95/02:00:00,298/02:00"
-        self.assertEqual(datetime(2003, 4, 6, 1, 59,
-                                  tzinfo=tz.tzstr(s)).tzname(), "EST")
-        self.assertEqual(datetime(2003, 4, 6, 2, 00,
-                                  tzinfo=tz.tzstr(s)).tzname(), "EDT")
-
-    def testStrEnd5(self):
-        s = "EST5EDT4,95/02:00:00,298/02"
-        self.assertEqual(datetime(2003, 10, 26, 0, 59,
-                                  tzinfo=tz.tzstr(s)).tzname(), "EDT")
-        end = tz.enfold(datetime(2003, 10, 26, 1, 00,
-                                 tzinfo=tz.tzstr(s)), fold=1)
-        self.assertEqual(end.tzname(), "EST")
-
-    def testStrStart6(self):
-        s = "EST5EDT4,J96/02:00:00,J299/02:00"
-        self.assertEqual(datetime(2003, 4, 6, 1, 59,
-                                  tzinfo=tz.tzstr(s)).tzname(), "EST")
-        self.assertEqual(datetime(2003, 4, 6, 2, 00,
-                                  tzinfo=tz.tzstr(s)).tzname(), "EDT")
-
-    def testStrEnd6(self):
-        s = "EST5EDT4,J96/02:00:00,J299/02"
-        self.assertEqual(datetime(2003, 10, 26, 0, 59,
-                                  tzinfo=tz.tzstr(s)).tzname(), "EDT")
-
-        end = tz.enfold(datetime(2003, 10, 26, 1, 00,
-                                 tzinfo=tz.tzstr(s)), fold=1)
-        self.assertEqual(end.tzname(), "EST")
-
     def testStrStr(self):
         # Test that tz.tzstr() won't throw an error if given a str instead
         # of a unicode literal.
@@ -1330,15 +1239,6 @@ def testStrStr(self):
         self.assertEqual(datetime(2003, 4, 6, 2, 00,
                                   tzinfo=tz.tzstr(str("EST5EDT"))).tzname(), "EDT")
 
-    def testStrCmp1(self):
-        self.assertEqual(tz.tzstr("EST5EDT"),
-                         tz.tzstr("EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00"))
-
-    def testStrCmp2(self):
-        # TODO: This is parsing the default arguments.
-        self.assertEqual(tz.tzstr("EST5EDT"),
-                         tz.tzstr("EST5EDT,4,1,0,7200,10,-1,0,7200,3600"))
-
     def testStrInequality(self):
         TZS1 = tz.tzstr('EST5EDT4')
 
@@ -1483,10 +1383,12 @@ def test_valid_GNU_tzstr(tz_str, expected):
         start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
         end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))),
 ])
-def test_valid_default_format(tz_str, expected):
-    # This tests the "default" format that is used widely in the tests and
-    # examples. It is unclear where this format originated from.
-    tzi = tz.tzstr(tz_str)
+def test_valid_dateutil_format(tz_str, expected):
+    # This tests the dateutil-specific format that is used widely in the tests
+    # and examples. It is unclear where this format originated from.
+    with pytest.warns(tz.DeprecatedTzFormatWarning):
+        tzi = tz.tzstr(tz_str)
+   
     assert tzi == expected
 
 
@@ -1504,6 +1406,53 @@ def test_invalid_GNU_tzstr(tz_str):
         tz.tzstr(tz_str)
 
 
+# Different representations of the same default rule set
+DEFAULT_TZSTR_RULES_EQUIV_2003 = [
+    'EST5EDT',
+    'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00',
+    'EST5EDT4,95/02:00:00,298/02:00',
+    'EST5EDT4,J96/02:00:00,J299/02:00',
+    'EST5EDT4,J96/02:00:00,J299/02'
+]
+
+
+@pytest.mark.tzstr
+@pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003)
+def test_tzstr_default_start(tz_str):
+    tzi = tz.tzstr(tz_str)
+    dt_std = datetime(2003, 4, 6, 1, 59, tzinfo=tzi)
+    dt_dst = datetime(2003, 4, 6, 2, 00, tzinfo=tzi)
+
+    assert get_timezone_tuple(dt_std) == EST_TUPLE
+    assert get_timezone_tuple(dt_dst) == EDT_TUPLE
+
+
+@pytest.mark.tzstr
+@pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003)
+def test_tzstr_default_end(tz_str):
+    tzi = tz.tzstr(tz_str)
+    dt_dst = datetime(2003, 10, 26, 0, 59, tzinfo=tzi)
+    dt_dst_ambig = datetime(2003, 10, 26, 1, 00, tzinfo=tzi)
+    dt_std_ambig = tz.enfold(dt_dst_ambig, fold=1)
+    dt_std = datetime(2003, 10, 26, 2, 00, tzinfo=tzi)
+
+    assert get_timezone_tuple(dt_dst) == EDT_TUPLE
+    assert get_timezone_tuple(dt_dst_ambig) == EDT_TUPLE
+    assert get_timezone_tuple(dt_std_ambig) == EST_TUPLE
+    assert get_timezone_tuple(dt_std) == EST_TUPLE
+
+
+@pytest.mark.tzstr
+@pytest.mark.parametrize('tzstr_1', ['EST5EDT',
+                                     'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00'])
+@pytest.mark.parametrize('tzstr_2', ['EST5EDT',
+                                     'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00'])
+def test_tzstr_default_cmp(tzstr_1, tzstr_2):
+    tz1 = tz.tzstr(tzstr_1)
+    tz2 = tz.tzstr(tzstr_2)
+
+    assert tz1 == tz2
+
 class TZICalTest(unittest.TestCase, TzFoldMixin):
     def _gettz_str_tuple(self, tzname):
         TZ_EST = (