diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst index 98d2a5e5cdf00e..083b2c6fe13467 100644 --- a/Doc/library/zipfile.rst +++ b/Doc/library/zipfile.rst @@ -178,7 +178,7 @@ ZipFile objects .. class:: ZipFile(file, mode='r', compression=ZIP_STORED, allowZip64=True, \ compresslevel=None, *, strict_timestamps=True, \ - metadata_encoding=None) + with_ext_timestamps=False, metadata_encoding=None) Open a ZIP file, where *file* can be a path to a file (a string), a file-like object or a :term:`path-like object`. @@ -227,6 +227,9 @@ ZipFile objects Similar behavior occurs with files newer than 2107-12-31, the timestamp is also set to the limit. + The *with_ext_timestamps* controls whether to fill the extended timestamps + when writing files to the archive. + When mode is ``'r'``, *metadata_encoding* may be set to the name of a codec, which will be used to decode metadata such as the names of members and ZIP comments. @@ -285,6 +288,9 @@ ZipFile objects Added support for specifying member name encoding for reading metadata in the zipfile's directory and file headers. + .. versionchanged:: next + Added the *with_ext_timestamps* keyword-only parameter. + .. method:: ZipFile.close() @@ -885,7 +891,8 @@ There is one classmethod to make a :class:`ZipInfo` instance for a filesystem file: .. classmethod:: ZipInfo.from_file(filename, arcname=None, *, \ - strict_timestamps=True) + strict_timestamps=True, \ + with_ext_timestamps=False) Construct a :class:`ZipInfo` instance for a file on the filesystem, in preparation for adding it to a zip file. @@ -902,6 +909,10 @@ file: Similar behavior occurs with files newer than 2107-12-31, the timestamp is also set to the limit. + Setting ``with_ext_timestamps=True`` fills the file's extended timestamps + to the extra data, which allows other ZIP tools to recover the timestamp + more accurately when extracting the file. + .. versionadded:: 3.6 .. versionchanged:: 3.6.2 @@ -910,6 +921,9 @@ file: .. versionchanged:: 3.8 Added the *strict_timestamps* keyword-only parameter. + .. versionchanged:: next + Added the *with_ext_timestamps* keyword-only parameter. + Instances have the following methods and attributes: diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index 4f20209927e7b3..92a4f117d2b337 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -676,6 +676,91 @@ def test_add_file_after_2107(self): zinfo = zipfp.getinfo(TESTFN) self.assertEqual(zinfo.date_time, (2107, 12, 31, 23, 59, 59)) + def test_add_file_with_ext_timestamp(self): + """Check that calling ZipFile.write() sets extra data according to + with_ext_timestamps parameter.""" + mtime = 946684800.123456 + mtime_ns = 946684800_123456789 + atime_ns = 946684800_987654321 + ctime_ns = 946684800_555555555 + + with mock.patch('os.stat_result.st_mtime', mtime), \ + mock.patch('os.stat_result.st_mtime_ns', mtime_ns), \ + mock.patch('os.stat_result.st_atime_ns', atime_ns), \ + mock.patch('os.stat_result.st_ctime_ns', ctime_ns): + + # with_ext_timestamps=False (default) + with zipfile.ZipFile(TESTFN2, "w") as zipfp: + zipfp.write(TESTFN) + + with zipfile.ZipFile(TESTFN2) as zipfp: + zinfo = zipfp.infolist()[0] + + self.assertEqual(zinfo.extra, b'') + + # with_ext_timestamps=True + with zipfile.ZipFile(TESTFN2, "w", with_ext_timestamps=True) as zipfp: + zipfp.write(TESTFN) + + with zipfile.ZipFile(TESTFN2) as zipfp: + zinfo = zipfp.infolist()[0] + + self.assertEqual(zinfo.date_time[0], 2000) + + # NTFS Extra Field (0x000a) + delta = 116444736000000000 + ntfs_field = struct.unpack_from('