Repository: dateutil/dateutil (Python)

Issue:
tzfile does not correctly parse files due to its ignoring of leap data
The relevant part in the tzfile implementation does not read from the file if leapcnt is given:

https://github.com/dateutil/dateutil/blob/master/dateutil/tz/tz.py#L309-L312:

```
        # Not used, for now
        # if leapcnt:
        #    leap = struct.unpack(">%dl" % (leapcnt*2),
        #                         fileobj.read(leapcnt*8))
```

...which is wrong ;-) as you have to read (or seek forward in the file) this data even if you don't intend to use it. I suspect this hasn't been a problem in practice as the following data parts (isstd and isgmt information) basically aren't used anymore, after they've been set as ttinfo attributes.



Hints:

Patch:
diff --git a/dateutil/tz/tz.py b/dateutil/tz/tz.py
--- a/dateutil/tz/tz.py
+++ b/dateutil/tz/tz.py
@@ -307,10 +307,10 @@ def __init__(self, fileobj, filename=None):
             # The pairs of values are sorted in ascending order
             # by time.
 
-            # Not used, for now
-            # if leapcnt:
-            #    leap = struct.unpack(">%dl" % (leapcnt*2),
-            #                         fileobj.read(leapcnt*8))
+            # Not used, for now (but read anyway for correct file position)
+            if leapcnt:
+                leap = struct.unpack(">%dl" % (leapcnt*2),
+                                     fileobj.read(leapcnt*8))
 
             # Then there are tzh_ttisstdcnt standard/wall
             # indicators, each stored as a one-byte value;


Test patch:
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
@@ -297,8 +297,35 @@ def testRoundNonFullMinutes(self):
     def testLeapCountDecodesProperly(self):
         # This timezone has leapcnt, and failed to decode until
         # Eugene Oden notified about the issue.
+
+        # As leap information is currently unused (and unstored) by tzfile() we
+        # can only indirectly test this: Take advantage of tzfile() not closing
+        # the input file if handed in as an opened file and assert that the
+        # full file content has been read by tzfile(). Note: For this test to
+        # work NEW_YORK must be in TZif version 1 format i.e. no more data
+        # after TZif v1 header + data has been read
+        fileobj = BytesIO(base64.b64decode(NEW_YORK))
+        tzc = tz.tzfile(fileobj)
+        # we expect no remaining file content now, i.e. zero-length; if there's
+        # still data we haven't read the file format correctly
+        remaining_tzfile_content = fileobj.read()
+        self.assertEqual(len(remaining_tzfile_content), 0)
+
+    def testIsStd(self):
+        # NEW_YORK tzfile contains this isstd information:
+        isstd_expected = (0, 0, 0, 1)
         tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK)))
-        self.assertEqual(datetime(2007, 3, 31, 20, 12).tzname(), None)  # What is the point of this?
+        # gather the actual information as parsed by the tzfile class
+        isstd = []
+        for ttinfo in tzc._ttinfo_list:
+            # ttinfo objects contain boolean values
+            isstd.append(int(ttinfo.isstd))
+        # ttinfo list may contain more entries than isstd file content
+        isstd = tuple(isstd[:len(isstd_expected)])
+        self.assertEqual(
+            isstd_expected, isstd,
+            "isstd UTC/local indicators parsed: %s != tzfile contents: %s"
+            % (isstd, isstd_expected))
 
     def testGettz(self):
         # bug 892569