Skip to content

gh-91279: ZipFile.writestr now respect SOURCE_DATE_EPOCH #124435

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

Merged
merged 3 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,12 @@ zipinfo

(Contributed by Bénédikt Tran in :gh:`123424`.)

* :meth:`zipfile.ZipFile.writestr` now respect ``SOURCE_DATE_EPOCH`` that
distributions can set centrally and have build tools consume this in order
to produce reproducible output.

(Contributed by Jiahao Li in :gh:`91279`.)

.. Add improved modules above alphabetically, not here at the end.

Optimizations
Expand Down
31 changes: 30 additions & 1 deletion Lib/test/test_zipfile/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from random import randint, random, randbytes

from test import archiver_tests
from test.support import script_helper
from test.support import script_helper, os_helper
from test.support import (
findfile, requires_zlib, requires_bz2, requires_lzma,
captured_stdout, captured_stderr, requires_subprocess
Expand Down Expand Up @@ -1781,6 +1781,35 @@ def test_writestr_extended_local_header_issue1202(self):
zinfo.flag_bits |= zipfile._MASK_USE_DATA_DESCRIPTOR # Include an extended local header.
orig_zip.writestr(zinfo, data)

def test_write_with_source_date_epoch(self):
with os_helper.EnvironmentVarGuard() as env:
# Set the SOURCE_DATE_EPOCH environment variable to a specific timestamp
env['SOURCE_DATE_EPOCH'] = "1735715999"

with zipfile.ZipFile(TESTFN, "w") as zf:
zf.writestr("test_source_date_epoch.txt", "Testing SOURCE_DATE_EPOCH")

with zipfile.ZipFile(TESTFN, "r") as zf:
zip_info = zf.getinfo("test_source_date_epoch.txt")
get_time = time.localtime(int(os.environ['SOURCE_DATE_EPOCH']))[:6]
# Compare each element of the date_time tuple
# Allow for a 1-second difference
for z_time, g_time in zip(zip_info.date_time, get_time):
self.assertAlmostEqual(z_time, g_time, delta=1)

def test_write_without_source_date_epoch(self):
if 'SOURCE_DATE_EPOCH' in os.environ:
del os.environ['SOURCE_DATE_EPOCH']

with zipfile.ZipFile(TESTFN, "w") as zf:
zf.writestr("test_no_source_date_epoch.txt", "Testing without SOURCE_DATE_EPOCH")

with zipfile.ZipFile(TESTFN, "r") as zf:
zip_info = zf.getinfo("test_no_source_date_epoch.txt")
current_time = time.localtime()[:6]
for z_time, c_time in zip(zip_info.date_time, current_time):
self.assertAlmostEqual(z_time, c_time, delta=1)

def test_close(self):
"""Check that the zipfile is closed after the 'with' block."""
with zipfile.ZipFile(TESTFN2, "w") as zipfp:
Expand Down
6 changes: 5 additions & 1 deletion Lib/zipfile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,11 @@ def _for_archive(self, archive: ZipFile) -> Self:

Return self.
"""
self.date_time = time.localtime(time.time())[:6]
# gh-91279: Set the SOURCE_DATE_EPOCH to a specific timestamp
epoch = os.environ.get('SOURCE_DATE_EPOCH')
get_time = int(epoch) if epoch else time.time()
self.date_time = time.localtime(get_time)[:6]

self.compress_type = archive.compression
self.compress_level = archive.compresslevel
if self.filename.endswith('/'): # pragma: no cover
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:meth:`zipfile.ZipFile.writestr` now respect ``SOURCE_DATE_EPOCH`` that
distributions can set centrally and have build tools consume this in order
to produce reproducible output.
Loading