https://github.com/python/cpython/commit/80c84641253a0562896667ffd1ba483c4e86a9fc
commit: 80c84641253a0562896667ffd1ba483c4e86a9fc
branch: 3.14
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-10-16T23:08:02Z
summary:

[3.14] gh-133998: Fix gzip file creation when time is out of range (#159592)

[3.15] gh-133998: Fix gzip file creation when time is out of range (GH-134278) 
(GH-150221)

(cherry picked from commit 1daad8a1630c9ee011f6ff3796c4e7aef243463b)



(cherry picked from commit b039d1bd976819320af15494a11c973e0eca02f1)

Co-authored-by: Miss Islington (bot) 
<[email protected]>
Co-authored-by: adang1345 <[email protected]>
Co-authored-by: Serhiy Storchaka <[email protected]>

files:
A Misc/NEWS.d/next/Library/2025-05-19-20-29-35.gh-issue-133998.KmElUw.rst
M Doc/library/gzip.rst
M Lib/gzip.py
M Lib/test/test_gzip.py

diff --git a/Doc/library/gzip.rst b/Doc/library/gzip.rst
index a3c98d2f42b0b1..a370ab5629041b 100644
--- a/Doc/library/gzip.rst
+++ b/Doc/library/gzip.rst
@@ -103,9 +103,13 @@ The module defines the following items:
    is no compression. The default is ``9``.
 
    The optional *mtime* argument is the timestamp requested by gzip. The time
-   is in Unix format, i.e., seconds since 00:00:00 UTC, January 1, 1970.
-   If *mtime* is omitted or ``None``, the current time is used. Use *mtime* = 0
-   to generate a compressed stream that does not depend on creation time.
+   is in Unix format, i.e., seconds since 00:00:00 UTC, January 1, 1970. Set
+   *mtime* to ``0`` to generate a compressed stream that does not depend on
+   creation time. If *mtime* is omitted or ``None``, the current time is used;
+   however, if the current time is outside the range 00:00:00 UTC, January 1,
+   1970 through 06:28:15 UTC, February 7, 2106, or explicitly passed *mtime*
+   argument is outside the range ``0`` to ``2**32-1``, then the value ``0``
+   is used instead.
 
    See below for the :attr:`mtime` attribute that is set when decompressing.
 
diff --git a/Lib/gzip.py b/Lib/gzip.py
index f3f9fa91c8161b..4dec70c54b2d46 100644
--- a/Lib/gzip.py
+++ b/Lib/gzip.py
@@ -189,9 +189,10 @@ def __init__(self, filename=None, mode=None,
 
         The optional mtime argument is the timestamp requested by gzip.
         The time is in Unix format, i.e., seconds since 00:00:00 UTC,
-        January 1, 1970.  If mtime is omitted or None, the current time
-        is used.  Use mtime = 0 to generate a compressed stream that does
-        not depend on creation time.
+        January 1, 1970.  Set mtime to 0 to generate a compressed stream
+        that does not depend on creation time.  If mtime is omitted or None,
+        the current time is used.  If the resulting mtime is outside the
+        range 0 to 2**32-1, then the value 0 is used instead.
 
         """
 
@@ -299,6 +300,8 @@ def _write_gzip_header(self, compresslevel):
         mtime = self._write_mtime
         if mtime is None:
             mtime = time.time()
+        if not 0 <= mtime < 2**32:
+            mtime = 0
         write32u(self.fileobj, int(mtime))
         if compresslevel == _COMPRESS_LEVEL_BEST:
             xfl = b'\002'
@@ -634,6 +637,8 @@ def compress(data, compresslevel=_COMPRESS_LEVEL_BEST, *, 
mtime=0):
     gzip_data = zlib.compress(data, level=compresslevel, wbits=31)
     if mtime is None:
         mtime = time.time()
+    if not 0 <= mtime < 2**32:
+        mtime = 0
     # Reuse gzip header created by zlib, replace mtime and OS byte for
     # consistency.
     header = struct.pack("<4sLBB", gzip_data, int(mtime), gzip_data[8], 255)
diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py
index ccbacc7c19b6e6..fc61eddd827a51 100644
--- a/Lib/test/test_gzip.py
+++ b/Lib/test/test_gzip.py
@@ -11,6 +11,7 @@
 import unittest
 import warnings
 from subprocess import PIPE, Popen
+from unittest import mock
 from test.support import catch_unraisable_exception
 from test.support import import_helper
 from test.support import os_helper
@@ -351,6 +352,26 @@ def test_mtime(self):
             self.assertEqual(dataRead, data1)
             self.assertEqual(fRead.mtime, mtime)
 
+    def test_mtime_out_of_range(self):
+        for mtime in (-1, 2**32):
+            with gzip.GzipFile(self.filename, 'w', mtime=mtime) as fWrite:
+                fWrite.write(data1)
+            with gzip.GzipFile(self.filename) as fRead:
+                fRead.read(1)
+                self.assertEqual(fRead.mtime, 0)
+            datac = gzip.compress(data1, mtime=mtime)
+            with gzip.GzipFile(fileobj=io.BytesIO(datac)) as fRead:
+                fRead.read(1)
+                self.assertEqual(fRead.mtime, 0)
+
+        for mtime in (-1, 2**32):
+            with mock.patch('time.time', return_value=float(mtime)):
+                with gzip.GzipFile(self.filename, 'w') as fWrite:
+                    fWrite.write(data1)
+                with gzip.GzipFile(self.filename) as fRead:
+                    fRead.read(1)
+                    self.assertEqual(fRead.mtime, 0)
+
     def test_metadata(self):
         mtime = 123456789
 
diff --git 
a/Misc/NEWS.d/next/Library/2025-05-19-20-29-35.gh-issue-133998.KmElUw.rst 
b/Misc/NEWS.d/next/Library/2025-05-19-20-29-35.gh-issue-133998.KmElUw.rst
new file mode 100644
index 00000000000000..77d92628beefac
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-19-20-29-35.gh-issue-133998.KmElUw.rst
@@ -0,0 +1,5 @@
+Fix :exc:`struct.error` exception when creating a file with
+:class:`gzip.GzipFile` or compressing data with :func:`gzip.compress`
+if the system time is outside the range 00:00:00 UTC, January 1, 1970
+through 06:28:15 UTC, February 7, 2106, or explicitly passed *mtime*
+argument is outside the range ``0`` to ``2**32-1``.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to