From bb911a2319365a4155e7398b4b7978589d8bed49 Mon Sep 17 00:00:00 2001 From: Amp Tell Date: Sat, 2 May 2026 00:39:58 +0200 Subject: [PATCH] gh-75707: tarfile: Add optional open() argument "mtime" (GH-138117) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This makes it possible to set the gzip header mtime field without overriding time.time(), making it useful when creating reproducible archives. * 📜🤖 Added by blurb_it. --------- Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Ethan Furman --- Doc/library/tarfile.rst | 4 +++ Lib/tarfile.py | 18 ++++++++----- Lib/test/test_tarfile.py | 27 +++++++++++++++++++ ...5-08-24-15-09-30.gh-issue-75707.GOWZrC.rst | 1 + 4 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-08-24-15-09-30.gh-issue-75707.GOWZrC.rst diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst index a86469bb9ad7..6f1e01cf5aa6 100644 --- a/Doc/library/tarfile.rst +++ b/Doc/library/tarfile.rst @@ -142,6 +142,10 @@ Some facts and figures: a Zstandard dictionary used to improve compression of smaller amounts of data. + For modes ``'w:gz'`` and ``'w|gz'``, :func:`tarfile.open` accepts the + keyword argument *mtime* to create a gzip archive header with that mtime. By + default, the mtime is set to the time of creation of the archive. + For special purposes, there is a second format for *mode*: ``'filemode|[compression]'``. :func:`tarfile.open` will return a :class:`TarFile` object that processes its data as a stream of blocks. No random seeking will diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 7f0b0b3c6325..4f47aaab9028 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -337,7 +337,7 @@ class _Stream: """ def __init__(self, name, mode, comptype, fileobj, bufsize, - compresslevel, preset): + compresslevel, preset, mtime): """Construct a _Stream object. """ self._extfileobj = True @@ -372,7 +372,7 @@ class _Stream: self.exception = zlib.error self._init_read_gz() else: - self._init_write_gz(compresslevel) + self._init_write_gz(compresslevel, mtime) elif comptype == "bz2": try: @@ -421,7 +421,7 @@ class _Stream: if hasattr(self, "closed") and not self.closed: self.close() - def _init_write_gz(self, compresslevel): + def _init_write_gz(self, compresslevel, mtime): """Initialize for writing with gzip compression. """ self.cmp = self.zlib.compressobj(compresslevel, @@ -429,7 +429,9 @@ class _Stream: -self.zlib.MAX_WBITS, self.zlib.DEF_MEM_LEVEL, 0) - timestamp = struct.pack("