[Python-checkins] bpo-34097: Add support for zipping files older than 1980-01-01 (GH-8270)

Victor Stinner webhook-mailer at python.org
Thu Aug 2 09:04:57 EDT 2018


https://github.com/python/cpython/commit/a2fe1e52eb94c41d9ebce1ab284180d7b1faa2a4
commit: a2fe1e52eb94c41d9ebce1ab284180d7b1faa2a4
branch: master
author: Marcel Plch <gmarcel.plch at gmail.com>
committer: Victor Stinner <vstinner at redhat.com>
date: 2018-08-02T15:04:52+02:00
summary:

bpo-34097: Add support for zipping files older than 1980-01-01 (GH-8270)

ZipFile can zip files older than 1980-01-01 and newer than 2107-12-31 using
a new strict_timestamps parameter at the cost of setting the timestamp
to the limit.

files:
A Misc/NEWS.d/next/Library/2018-07-13-13-42-10.bpo-34097.F5Dk5o.rst
M Doc/library/zipfile.rst
M Lib/test/test_zipfile.py
M Lib/zipfile.py

diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst
index c0f2a89a3a17..a2fa9666acc1 100644
--- a/Doc/library/zipfile.rst
+++ b/Doc/library/zipfile.rst
@@ -368,7 +368,7 @@ ZipFile Objects
 
 
 .. method:: ZipFile.write(filename, arcname=None, compress_type=None, \
-                          compresslevel=None)
+                          compresslevel=None, *, strict_timestamps=True)
 
    Write the file named *filename* to the archive, giving it the archive name
    *arcname* (by default, this will be the same as *filename*, but without a drive
@@ -377,6 +377,11 @@ ZipFile Objects
    the new entry. Similarly, *compresslevel* will override the constructor if
    given.
    The archive must be open with mode ``'w'``, ``'x'`` or ``'a'``.
+   The *strict_timestamps* argument, when set to ``False``, allows to
+   zip files older than 1980-01-01 at the cost of setting the
+   timestamp to 1980-01-01.
+   Similar behavior occurs with files newer than 2107-12-31,
+   the timestamp is also set to the limit.
 
    .. note::
 
@@ -400,6 +405,9 @@ ZipFile Objects
       a closed ZipFile will raise a :exc:`ValueError`.  Previously,
       a :exc:`RuntimeError` was raised.
 
+   .. versionadded:: 3.8
+      The *strict_timestamps* keyword-only argument
+
 
 .. method:: ZipFile.writestr(zinfo_or_arcname, data, compress_type=None, \
                              compresslevel=None)
@@ -540,7 +548,8 @@ information about a single member of the ZIP archive.
 There is one classmethod to make a :class:`ZipInfo` instance for a filesystem
 file:
 
-.. classmethod:: ZipInfo.from_file(filename, arcname=None)
+.. classmethod:: ZipInfo.from_file(filename, arcname=None, *, \
+                                   strict_timestamps=True)
 
    Construct a :class:`ZipInfo` instance for a file on the filesystem, in
    preparation for adding it to a zip file.
@@ -551,11 +560,20 @@ file:
    If *arcname* is not specified, the name will be the same as *filename*, but
    with any drive letter and leading path separators removed.
 
+   The *strict_timestamps* argument, when set to ``False``, allows to
+   zip files older than 1980-01-01 at the cost of setting the
+   timestamp to 1980-01-01.
+   Similar behavior occurs with files newer than 2107-12-31,
+   the timestamp is also set to the limit.
+
    .. versionadded:: 3.6
 
    .. versionchanged:: 3.6.2
       The *filename* parameter accepts a :term:`path-like object`.
 
+   .. versionadded:: 3.8
+      The *strict_timestamps* keyword-only argument
+
 
 Instances have the following methods and attributes:
 
diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py
index ac9a4ff6fef0..3b78b7f16759 100644
--- a/Lib/test/test_zipfile.py
+++ b/Lib/test/test_zipfile.py
@@ -549,6 +549,22 @@ def test_add_file_before_1980(self):
         with zipfile.ZipFile(TESTFN2, "w") as zipfp:
             self.assertRaises(ValueError, zipfp.write, TESTFN)
 
+        with zipfile.ZipFile(TESTFN2, "w") as zipfp:
+            zipfp.write(TESTFN, strict_timestamps=False)
+            zinfo = zipfp.getinfo(TESTFN)
+            self.assertEqual(zinfo.date_time, (1980, 1, 1, 0, 0, 0))
+
+    def test_add_file_after_2107(self):
+        # Set atime and mtime to 2108-12-30
+        os.utime(TESTFN, (4386268800, 4386268800))
+        with zipfile.ZipFile(TESTFN2, "w") as zipfp:
+            self.assertRaises(struct.error, zipfp.write, TESTFN)
+
+        with zipfile.ZipFile(TESTFN2, "w") as zipfp:
+            zipfp.write(TESTFN, strict_timestamps=False)
+            zinfo = zipfp.getinfo(TESTFN)
+            self.assertEqual(zinfo.date_time, (2107, 12, 31, 23, 59, 59))
+
 
 @requires_zlib
 class DeflateTestsWithSourceFile(AbstractTestsWithSourceFile,
diff --git a/Lib/zipfile.py b/Lib/zipfile.py
index 2757ce91cf48..6da1778873dd 100644
--- a/Lib/zipfile.py
+++ b/Lib/zipfile.py
@@ -469,7 +469,7 @@ def _decodeExtra(self):
             extra = extra[ln+4:]
 
     @classmethod
-    def from_file(cls, filename, arcname=None):
+    def from_file(cls, filename, arcname=None, *, strict_timestamps=True):
         """Construct an appropriate ZipInfo for a file on the filesystem.
 
         filename should be the path to a file or directory on the filesystem.
@@ -484,6 +484,10 @@ def from_file(cls, filename, arcname=None):
         isdir = stat.S_ISDIR(st.st_mode)
         mtime = time.localtime(st.st_mtime)
         date_time = mtime[0:6]
+        if not strict_timestamps and date_time[0] < 1980:
+            date_time = (1980, 1, 1, 0, 0, 0)
+        elif not strict_timestamps and date_time[0] > 2107:
+            date_time = (2107, 12, 31, 23, 59, 59)
         # Create ZipInfo instance to store file information
         if arcname is None:
             arcname = filename
@@ -1673,7 +1677,8 @@ def _writecheck(self, zinfo):
                                    " would require ZIP64 extensions")
 
     def write(self, filename, arcname=None,
-              compress_type=None, compresslevel=None):
+              compress_type=None, compresslevel=None, *,
+              strict_timestamps=True):
         """Put the bytes from filename into the archive under the name
         arcname."""
         if not self.fp:
@@ -1684,7 +1689,8 @@ def write(self, filename, arcname=None,
                 "Can't write to ZIP archive while an open writing handle exists"
             )
 
-        zinfo = ZipInfo.from_file(filename, arcname)
+        zinfo = ZipInfo.from_file(filename, arcname,
+                                  strict_timestamps=strict_timestamps)
 
         if zinfo.is_dir():
             zinfo.compress_size = 0
diff --git a/Misc/NEWS.d/next/Library/2018-07-13-13-42-10.bpo-34097.F5Dk5o.rst b/Misc/NEWS.d/next/Library/2018-07-13-13-42-10.bpo-34097.F5Dk5o.rst
new file mode 100644
index 000000000000..397149559af5
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-07-13-13-42-10.bpo-34097.F5Dk5o.rst
@@ -0,0 +1,3 @@
+ZipFile can zip files older than 1980-01-01 and newer than 2107-12-31 using
+a new ``strict_timestamps`` parameter at the cost of setting the timestamp
+to the limit.



More information about the Python-checkins mailing list