From eb4708f82be28c8e47e7202e5620bf9b0631acc2 Mon Sep 17 00:00:00 2001 From: Branch Vincent Date: Tue, 10 Jun 2025 19:10:05 -0700 Subject: [PATCH] Support a SOURCE_DATE_EPOCH prior to 1980 --- backend/src/hatchling/builders/wheel.py | 4 +++- tests/backend/builders/test_wheel.py | 13 ++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/src/hatchling/builders/wheel.py b/backend/src/hatchling/builders/wheel.py index cf7c236af..2d844a5fc 100644 --- a/backend/src/hatchling/builders/wheel.py +++ b/backend/src/hatchling/builders/wheel.py @@ -88,7 +88,9 @@ def __init__(self, project_id: str, *, reproducible: bool) -> None: def get_reproducible_time_tuple() -> TIME_TUPLE: from datetime import datetime, timezone - d = datetime.fromtimestamp(get_reproducible_timestamp(), timezone.utc) + # `zipfile.ZipInfo` does not support timestamps before 1980 + min_ts = 315532800 # 1980-01-01T00:00:00Z + d = datetime.fromtimestamp(max(get_reproducible_timestamp(), min_ts), timezone.utc) return d.year, d.month, d.day, d.hour, d.minute, d.second def add_file(self, included_file: IncludedFile) -> tuple[str, str, str]: diff --git a/tests/backend/builders/test_wheel.py b/tests/backend/builders/test_wheel.py index b670b69dd..acd453875 100644 --- a/tests/backend/builders/test_wheel.py +++ b/tests/backend/builders/test_wheel.py @@ -786,7 +786,14 @@ def test_default_auto_detection(self, hatch, helpers, temp_dir, config_file): zip_info = zip_archive.getinfo(f"{metadata_directory}/WHEEL") assert zip_info.date_time == (2020, 2, 2, 0, 0, 0) - def test_default_reproducible_timestamp(self, hatch, helpers, temp_dir, config_file): + @pytest.mark.parametrize( + ("epoch", "expected_date_time"), + [ + ("0", (1980, 1, 1, 0, 0, 0)), + ("1580601700", (2020, 2, 2, 0, 1, 40)), + ], + ) + def test_default_reproducible_timestamp(self, hatch, helpers, temp_dir, config_file, epoch, expected_date_time): config_file.model.template.plugins["default"]["src-layout"] = False config_file.save() @@ -812,7 +819,7 @@ def test_default_reproducible_timestamp(self, hatch, helpers, temp_dir, config_f build_path = project_path / "dist" build_path.mkdir() - with project_path.as_cwd(env_vars={"SOURCE_DATE_EPOCH": "1580601700"}): + with project_path.as_cwd(env_vars={"SOURCE_DATE_EPOCH": epoch}): artifacts = list(builder.build(directory=str(build_path))) assert len(artifacts) == 1 @@ -837,7 +844,7 @@ def test_default_reproducible_timestamp(self, hatch, helpers, temp_dir, config_f with zipfile.ZipFile(str(expected_artifact), "r") as zip_archive: zip_info = zip_archive.getinfo(f"{metadata_directory}/WHEEL") - assert zip_info.date_time == (2020, 2, 2, 0, 1, 40) + assert zip_info.date_time == expected_date_time def test_default_no_reproducible(self, hatch, helpers, temp_dir, config_file): config_file.model.template.plugins["default"]["src-layout"] = False