From 4c389c901276ab1c6a649ffaed5c4767e1c9caba Mon Sep 17 00:00:00 2001 From: ctrlaltf2 <23644849+ctrlaltf2@users.noreply.github.com> Date: Mon, 19 May 2025 14:07:15 -0400 Subject: [PATCH 1/7] gh-134261: Don't rely on local time for reproducible builds & tests --- Lib/test/test_zipfile/test_core.py | 2 +- Lib/zipfile/__init__.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index 43056978848c03..fbeddd654cc00a 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -1812,7 +1812,7 @@ def test_write_with_source_date_epoch(self): 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] + get_time = time.gmtime(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): diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py index 894b4d37233923..bc34309759e8ed 100644 --- a/Lib/zipfile/__init__.py +++ b/Lib/zipfile/__init__.py @@ -623,9 +623,12 @@ def _for_archive(self, archive): Return self. """ # 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] + source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH') + + if source_date_epoch: + self.date_time = time.gmtime(int(source_date_epoch))[:6] + else: + self.date_time = time.localtime(time.time())[:6] self.compress_type = archive.compression self.compress_level = archive.compresslevel From e0fcc2d0eb4e83a43a16df242f1c0c5cf6871495 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 21:08:26 +0000 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-05-19-21-08-25.gh-issue-134261.ravGYm.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-05-19-21-08-25.gh-issue-134261.ravGYm.rst diff --git a/Misc/NEWS.d/next/Library/2025-05-19-21-08-25.gh-issue-134261.ravGYm.rst b/Misc/NEWS.d/next/Library/2025-05-19-21-08-25.gh-issue-134261.ravGYm.rst new file mode 100644 index 00000000000000..3791f9241355c6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-19-21-08-25.gh-issue-134261.ravGYm.rst @@ -0,0 +1 @@ +zip: On reproducible builds, ZipFile longer pulls in local environment timezone when writing file datetimes From f7c52b8c71c641d8e0d45b343e33bc6bbdbe79de Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 19 May 2026 11:21:30 -0700 Subject: [PATCH 3/7] Materialize the expected value for the timestamp. --- Lib/test/test_zipfile/test_core.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index f464b638b179e0..f3fae8255c1efa 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -1812,11 +1812,8 @@ def test_write_with_source_date_epoch(self): with zipfile.ZipFile(TESTFN, "r") as zf: zip_info = zf.getinfo("test_source_date_epoch.txt") - get_time = time.gmtime(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) + expected_utc = (1975, 7, 2, 22, 19, 59) + self.assertEqual(zip_info.date_time, expected_utc) def test_write_without_source_date_epoch(self): with os_helper.EnvironmentVarGuard() as env: From 06f66b327d3173ae7c58f034239380c42efa74bf Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 19 May 2026 11:33:39 -0700 Subject: [PATCH 4/7] Correct stable timestamp. --- Lib/test/test_zipfile/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index f3fae8255c1efa..78aebb65e174fa 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -1812,7 +1812,7 @@ def test_write_with_source_date_epoch(self): with zipfile.ZipFile(TESTFN, "r") as zf: zip_info = zf.getinfo("test_source_date_epoch.txt") - expected_utc = (1975, 7, 2, 22, 19, 59) + expected_utc = (2025, 1, 1, 7, 19, 59) self.assertEqual(zip_info.date_time, expected_utc) def test_write_without_source_date_epoch(self): From 5c094226fda0372a61c46198b905684ab5e225c8 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 19 May 2026 11:48:59 -0700 Subject: [PATCH 5/7] Seems a second gets lost during the zip file creation. --- Lib/test/test_zipfile/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index 78aebb65e174fa..2aa0880039c3fa 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -1812,7 +1812,7 @@ def test_write_with_source_date_epoch(self): with zipfile.ZipFile(TESTFN, "r") as zf: zip_info = zf.getinfo("test_source_date_epoch.txt") - expected_utc = (2025, 1, 1, 7, 19, 59) + expected_utc = (2025, 1, 1, 7, 19, 58) self.assertEqual(zip_info.date_time, expected_utc) def test_write_without_source_date_epoch(self): From 66499d30ebd46e1e1a4582d3bc64082edc286a6e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 19 May 2026 12:00:20 -0700 Subject: [PATCH 6/7] Implement a proper timestamp comparison with tolerance. --- Lib/test/test_zipfile/test_core.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index 2aa0880039c3fa..847348031c8898 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -1824,9 +1824,13 @@ def test_write_without_source_date_epoch(self): 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) + self.assertTimestampAlmostEqual(time.localtime(), zip_info.date_time, tolerance=1) + + def assertTimestampAlmostEqual(self, time1, time2, tolerance): + import datetime + dt1 = datetime.datetime(*time1[:6]) + dt2 = datetime.datetime(*time2[:6]) + self.assertLessEqual((dt1 - dt2).total_seconds(), tolerance) def test_close(self): """Check that the zipfile is closed after the 'with' block.""" From 0f962dc8601f31d7b9e1be6745a7cf7c4d5ef94e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 19 May 2026 12:35:07 -0700 Subject: [PATCH 7/7] Nicer blurb message. Co-authored-by: Emma Smith --- .../next/Library/2025-05-19-21-08-25.gh-issue-134261.ravGYm.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-05-19-21-08-25.gh-issue-134261.ravGYm.rst b/Misc/NEWS.d/next/Library/2025-05-19-21-08-25.gh-issue-134261.ravGYm.rst index 3791f9241355c6..bf552fee814acb 100644 --- a/Misc/NEWS.d/next/Library/2025-05-19-21-08-25.gh-issue-134261.ravGYm.rst +++ b/Misc/NEWS.d/next/Library/2025-05-19-21-08-25.gh-issue-134261.ravGYm.rst @@ -1 +1 @@ -zip: On reproducible builds, ZipFile longer pulls in local environment timezone when writing file datetimes +zip: On reproducible builds, ZipFile uses UTC instead of the local time when writing file datetimes to avoid underflows.