Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-34097: Add support for zipping files older than 1980-01-01 #8270

Merged
merged 13 commits into from Aug 2, 2018
22 changes: 20 additions & 2 deletions Doc/library/zipfile.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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::

Expand All @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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:

Expand Down
16 changes: 16 additions & 0 deletions Lib/test/test_zipfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 9 additions & 3 deletions Lib/zipfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the largest year is 2107. Years after 2017 should also be clamped to 2017-12-31, no?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

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
Expand Down Expand Up @@ -1674,7 +1678,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:
Expand All @@ -1685,7 +1690,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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.