Skip to content

Commit a2fe1e5

Browse files
Marcel Plchvstinner
authored andcommitted
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.
1 parent fc512e3 commit a2fe1e5

File tree

4 files changed

+48
-5
lines changed

4 files changed

+48
-5
lines changed

Doc/library/zipfile.rst

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ ZipFile Objects
368368

369369

370370
.. method:: ZipFile.write(filename, arcname=None, compress_type=None, \
371-
compresslevel=None)
371+
compresslevel=None, *, strict_timestamps=True)
372372
373373
Write the file named *filename* to the archive, giving it the archive name
374374
*arcname* (by default, this will be the same as *filename*, but without a drive
@@ -377,6 +377,11 @@ ZipFile Objects
377377
the new entry. Similarly, *compresslevel* will override the constructor if
378378
given.
379379
The archive must be open with mode ``'w'``, ``'x'`` or ``'a'``.
380+
The *strict_timestamps* argument, when set to ``False``, allows to
381+
zip files older than 1980-01-01 at the cost of setting the
382+
timestamp to 1980-01-01.
383+
Similar behavior occurs with files newer than 2107-12-31,
384+
the timestamp is also set to the limit.
380385

381386
.. note::
382387

@@ -400,6 +405,9 @@ ZipFile Objects
400405
a closed ZipFile will raise a :exc:`ValueError`. Previously,
401406
a :exc:`RuntimeError` was raised.
402407

408+
.. versionadded:: 3.8
409+
The *strict_timestamps* keyword-only argument
410+
403411

404412
.. method:: ZipFile.writestr(zinfo_or_arcname, data, compress_type=None, \
405413
compresslevel=None)
@@ -540,7 +548,8 @@ information about a single member of the ZIP archive.
540548
There is one classmethod to make a :class:`ZipInfo` instance for a filesystem
541549
file:
542550

543-
.. classmethod:: ZipInfo.from_file(filename, arcname=None)
551+
.. classmethod:: ZipInfo.from_file(filename, arcname=None, *, \
552+
strict_timestamps=True)
544553

545554
Construct a :class:`ZipInfo` instance for a file on the filesystem, in
546555
preparation for adding it to a zip file.
@@ -551,11 +560,20 @@ file:
551560
If *arcname* is not specified, the name will be the same as *filename*, but
552561
with any drive letter and leading path separators removed.
553562

563+
The *strict_timestamps* argument, when set to ``False``, allows to
564+
zip files older than 1980-01-01 at the cost of setting the
565+
timestamp to 1980-01-01.
566+
Similar behavior occurs with files newer than 2107-12-31,
567+
the timestamp is also set to the limit.
568+
554569
.. versionadded:: 3.6
555570

556571
.. versionchanged:: 3.6.2
557572
The *filename* parameter accepts a :term:`path-like object`.
558573

574+
.. versionadded:: 3.8
575+
The *strict_timestamps* keyword-only argument
576+
559577

560578
Instances have the following methods and attributes:
561579

Lib/test/test_zipfile.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,22 @@ def test_add_file_before_1980(self):
549549
with zipfile.ZipFile(TESTFN2, "w") as zipfp:
550550
self.assertRaises(ValueError, zipfp.write, TESTFN)
551551

552+
with zipfile.ZipFile(TESTFN2, "w") as zipfp:
553+
zipfp.write(TESTFN, strict_timestamps=False)
554+
zinfo = zipfp.getinfo(TESTFN)
555+
self.assertEqual(zinfo.date_time, (1980, 1, 1, 0, 0, 0))
556+
557+
def test_add_file_after_2107(self):
558+
# Set atime and mtime to 2108-12-30
559+
os.utime(TESTFN, (4386268800, 4386268800))
560+
with zipfile.ZipFile(TESTFN2, "w") as zipfp:
561+
self.assertRaises(struct.error, zipfp.write, TESTFN)
562+
563+
with zipfile.ZipFile(TESTFN2, "w") as zipfp:
564+
zipfp.write(TESTFN, strict_timestamps=False)
565+
zinfo = zipfp.getinfo(TESTFN)
566+
self.assertEqual(zinfo.date_time, (2107, 12, 31, 23, 59, 59))
567+
552568

553569
@requires_zlib
554570
class DeflateTestsWithSourceFile(AbstractTestsWithSourceFile,

Lib/zipfile.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ def _decodeExtra(self):
469469
extra = extra[ln+4:]
470470

471471
@classmethod
472-
def from_file(cls, filename, arcname=None):
472+
def from_file(cls, filename, arcname=None, *, strict_timestamps=True):
473473
"""Construct an appropriate ZipInfo for a file on the filesystem.
474474
475475
filename should be the path to a file or directory on the filesystem.
@@ -484,6 +484,10 @@ def from_file(cls, filename, arcname=None):
484484
isdir = stat.S_ISDIR(st.st_mode)
485485
mtime = time.localtime(st.st_mtime)
486486
date_time = mtime[0:6]
487+
if not strict_timestamps and date_time[0] < 1980:
488+
date_time = (1980, 1, 1, 0, 0, 0)
489+
elif not strict_timestamps and date_time[0] > 2107:
490+
date_time = (2107, 12, 31, 23, 59, 59)
487491
# Create ZipInfo instance to store file information
488492
if arcname is None:
489493
arcname = filename
@@ -1673,7 +1677,8 @@ def _writecheck(self, zinfo):
16731677
" would require ZIP64 extensions")
16741678

16751679
def write(self, filename, arcname=None,
1676-
compress_type=None, compresslevel=None):
1680+
compress_type=None, compresslevel=None, *,
1681+
strict_timestamps=True):
16771682
"""Put the bytes from filename into the archive under the name
16781683
arcname."""
16791684
if not self.fp:
@@ -1684,7 +1689,8 @@ def write(self, filename, arcname=None,
16841689
"Can't write to ZIP archive while an open writing handle exists"
16851690
)
16861691

1687-
zinfo = ZipInfo.from_file(filename, arcname)
1692+
zinfo = ZipInfo.from_file(filename, arcname,
1693+
strict_timestamps=strict_timestamps)
16881694

16891695
if zinfo.is_dir():
16901696
zinfo.compress_size = 0
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ZipFile can zip files older than 1980-01-01 and newer than 2107-12-31 using
2+
a new ``strict_timestamps`` parameter at the cost of setting the timestamp
3+
to the limit.

0 commit comments

Comments
 (0)