Fix ZIP Timestamps Error in Homebrew Python Hatchling
Resolve 'ZIP does not support timestamps before 1980' error in Homebrew Python formulas like kosmorro with termcolor 3.3.0 and hatchling. Set SOURCE_DATE_EPOCH to clamp timestamps and fix wheel builds during pip install.
How to fix ‘ZIP does not support timestamps before 1980’ error in Homebrew formula installation for Python package (termcolor with hatchling)?
I maintain a custom Homebrew tap with a formula for Kosmorro (ephemerides calculation program). After updating the termcolor resource from version 2.5.0 to 3.3.0, adding pytz resource and python-argcomplete dependency, and updating url/sha256 values, brew install --verbose kosmorro fails during pip installation of termcolor.
Formula (kosmorro.rb)
class Kosmorro < Formula
include Language::Python::Virtualenv
desc "Ephemerides calculation program"
homepage "https://kosmorro.space"
url "https://files.pythonhosted.org/packages/8e/62/9c1f4377a50615be7046e3c6112ea7238bc5296d622946a3b360b6807765/kosmorro-1.0.1.tar.gz"
sha256 "7eabe34410ace99d850786665276bf417ed33df998376adbc0af2d1c4431b873"
revision 1
depends_on "python@3.14"
depends_on "certifi"
depends_on "numpy"
depends_on "python-argcomplete"
resource "babel" do
url "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz"
sha256 "0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"
end
resource "jplephem" do
url "https://files.pythonhosted.org/packages/3c/42/0545c37e070e5f940eb52987fb25a351ab3c9327b40bab6ad3dd6125b2e1/jplephem-2.23.tar.gz"
sha256 "d3fb9477e4bf4c39d10497d4ff15e5271b7ac03fa101e1821aac527d646eccf9"
end
resource "kosmorrolib" do
url "https://files.pythonhosted.org/packages/b0/04/699202c54fbcd60d8935a61faccb1ca4bcb2b5e53c13102ed476402e5f6f/kosmorrolib-1.0.13.tar.gz"
sha256 "d327a3f20f485e46f520c2c55d573fcbe804bdb40131946f815feabc8a964d39"
end
resource "python-dateutil" do
url "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz"
sha256 "37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"
end
resource "pytz" do
url "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz"
sha256 "360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"
end
resource "sgp4" do
url "https://files.pythonhosted.org/packages/6e/d0/fc467010d17742321f73b16a71acac88439a88f2b166641942a6566c9b2a/sgp4-2.25.tar.gz"
sha256 "e19edc6dcc25d69fb8fde0a267b8f0c44d7e915c7bcbeacf5d3a8b595baf0674"
end
resource "six" do
url "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz"
sha256 "ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"
end
resource "skyfield" do
url "https://files.pythonhosted.org/packages/2d/cb/0c9f9685f6ff3b9c268b1d995c584f0d09de160f46fbd6c1df0631565bfd/skyfield-1.53.tar.gz"
sha256 "24099855f3ba3906663ac1c10e650041e747680b986e807400eddedc0be4a8b4"
end
resource "skyfield-data" do
url "https://files.pythonhosted.org/packages/d3/6a/2f8d001dfcacb737cbdbc96db566eb9de9ebf0493bf9780785c42215c397/skyfield_data-7.0.0.tar.gz"
sha256 "df3b4f0f5b5b57e5adf5a0a5d30d73ca23dd4066ee666da3222d7bd63d580bbd"
end
resource "tabulate" do
url "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz"
sha256 "0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"
end
resource "termcolor" do
url "https://files.pythonhosted.org/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz"
sha256 "348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5"
end
def install
virtualenv_install_with_resources
end
test do
assert_includes shell_output("#{bin}/kosmorro --version"), "Kosmorro #{version}\n"
end
end
Error Output (key parts)
==> python3.14 -m pip ... install ... termcolor-3.3.0
...
ValueError: ZIP does not support timestamps before 1980
error: subprocess-exited-with-error
× Preparing metadata (pyproject.toml) did not run successfully.
│ exit code: 1
...
ERROR: Failed to build 'file:///private/tmp/kosmorro--termcolor-20260108-17232-gwhic8/termcolor-3.3.0' when installing build dependencies
The traceback points to hatchling during wheel building for hatch-vcs build dependencies, specifically in zipfile.ZipInfo.
Python 3.14 is used (was already before). Installation worked fine prior to these changes.
Is this due to termcolor 3.3.0 using newer build tools (hatchling/hatch-vcs) incompatible with Homebrew’s environment? How to resolve this in the formula (e.g., patch timestamps, use bottles, alternative build method)?
Set SOURCE_DATE_EPOCH to a value >= 315532800 (1980-01-01 UTC) in your formula before the virtualenv install — for example add ENV['SOURCE_DATE_EPOCH'] = [Time.now.to_i, 315532800].max.to_s to kosmorro.rb. That prevents the python zipfile (zipfile.ZipInfo) error during hatchling python wheel builds; upgrading to a hatchling release that clamps timestamps is a longer-term fix.
Contents
- Homebrew formula fix: set SOURCE_DATE_EPOCH (homebrew python)
- Why the error happens (python zipfile)
- Alternative resolutions: upgrade hatchling, bottles, pinning (hatchling python)
- How to reproduce, test, and debug (zip timestamps)
- Patch example for kosmorro.rb
- Sources
- Conclusion
Homebrew formula fix: set SOURCE_DATE_EPOCH (homebrew python)
Short version: add a build-time environment variable in the formula so wheel builders stamp files with a legal DOS timestamp.
Why this is the least-invasive fix
- It’s tiny and local to the kosmorro build.
- It avoids changing packaging backends or vendoring patched packages.
- It fixes the immediate ValueError from Python’s zip handling.
Recommended code (insert before virtualenv install):
def install
# Ensure any build-time timestamp is at least 1980-01-01 UTC
ENV["SOURCE_DATE_EPOCH"] = [Time.now.to_i, 315532800].max.to_s
# Optional: make conversions use UTC to avoid negative local tz offsets
# ENV["TZ"] = "UTC"
virtualenv_install_with_resources
end
Notes
315532800is the Unix timestamp for 1980-01-01 00:00:00 UTC. Zip stores DOS timestamps and zipfile rejects dates earlier than that.- Using the clamp
[Time.now.to_i, 315532800].max.to_skeeps builds non-statically-dated on normal machines while guaranteeing the DOS minimum. - You can instead hard-code
"315532800"if you want fully reproducible build timestamps for this formula. - If you prefer, set
ENV["TZ"]="UTC"as an extra safeguard against build-time conversions to a negative local offset (see the CPython issue linked below). That’s optional.
After the change:
- Rebuild from source:
brew reinstall --build-from-source --verbose kosmorro(orbrew install --build-from-source kosmorro). - The pip/hatchling wheel build step should no longer raise
ValueError: ZIP does not support timestamps before 1980.
Why the error happens (python zipfile)
Short explanation: Zip archives store file modification times using a DOS date/time format that has a minimum of 1980‑01‑01. If a build backend stamps files with an epoch timestamp before that, Python’s zipfile.ZipInfo raises a ValueError.
Key mechanics
- Many PEP 517 build backends (hatchling, setuptools, etc.) set file mtimes in generated wheels using
SOURCE_DATE_EPOCHwhen that environment variable exists. The reproducible-build ecosystem relies on that variable to make builds deterministic. - If
SOURCE_DATE_EPOCHis 0 or otherwise earlier than 1980, wheel builders may use it directly (or convert it to local time incorrectly), producing a pre‑1980 timestamp thatzipfile.ZipInforejects. - There have been concrete reports and fixes: see the CPython issue about negative timezone offsets and zip timestamp underflow and the hatchling PR that clamps timestamps to 1980 to avoid the problem.
Relevant reads
- Homebrew documents reproducible builds and explicitly notes ZIP/ DOS timestamp constraints and the need for a value >= 315532800: https://docs.brew.sh/Reproducible-Builds
- The Reproducible Builds project documents SOURCE_DATE_EPOCH and recommends clamping for ZIP: https://reproducible-builds.org/docs/source-date-epoch/
- CPython issue describing timezone conversion underflow behavior: https://github.com/python/cpython/issues/134261
- The hatchling PR that implements a clamp to 1980 for build timestamps: https://github.com/pypa/hatch/pull/1999
In practice this means: the termcolor 3.3.0 packaging (or one of its build-time dependencies such as hatch-vcs/hatchling) ends up stamping files with a timestamp that zip rejects. Homebrew’s build environment can expose this when SOURCE_DATE_EPOCH is set to a value earlier than 1980 (or when a backend mishandles timezone conversion).
Alternative resolutions: upgrade hatchling, bottles, pinning (hatchling python)
If you prefer not to change the formula environment, other options exist — each has tradeoffs.
- Wait for / require a patched hatchling
- The upstream fix clamps timestamps to 1980 in the builder (see the hatch PR). Once that version is on PyPI and picked up during the isolated build, the problem disappears.
- Pro: upstream, permanent solution.
- Con: you have to wait for new releases and for pip to install them during PEP 517 builds.
- Vendor or patch the build backend during the build
- You could vendor a patched hatchling or apply a small patch to the wheel-builder in the build environment.
- Pro: immediate and surgical.
- Con: more brittle, complicated in Homebrew because PEP 517 build isolation installs build dependencies into a temporary environment that Homebrew formulae don’t control easily.
- Use a bottle (prebuild)
- Build and publish a bottled kosmorro so end users install the binary instead of building wheels on their machines.
- Pro: avoids building termcolor (and others) on the user’s machine.
- Con: extra CI work and you must update bottles for new releases/architectures.
- Pin termcolor to an older release (temporary)
- Roll back to termcolor 2.5.0 in the resource if that version uses a different build backend.
- Pro: quick workaround.
- Con: avoids the newer release and is temporary.
- Patch formula to clamp SOURCE_DATE_EPOCH (recommended)
- Minimal, robust, and local. See the example in Homebrew’s own python formula for how Homebrew handles SOURCE_DATE_EPOCH in other formulas.
How to reproduce, test, and debug (zip timestamps)
Commands to reproduce locally (simulate the problematic environment)
- Reproduce the failing build step outside Homebrew:
- env SOURCE_DATE_EPOCH=0 python3.14 -m pip install --no-binary :all: --no-build-isolation --verbose termcolor==3.3.0
- You should see the hatchling wheel build step and a traceback referencing
zipfile.ZipInfoand the ValueError if the environment triggers a pre‑1980 timestamp.
Check your Homebrew build environment
- Run Homebrew with verbose/debug to see environment variables:
- brew install --build-from-source --verbose --debug kosmorro
- Inspect the build temporary path shown in the failure logs (e.g.,
/private/tmp/.../termcolor-3.3.0) to confirmSOURCE_DATE_EPOCHin that environment.
Quick Python check for the zip behavior
- This demonstrates the acceptance/rejection of DOS timestamps:
# save as test_zip.py and run with python3.14
import zipfile
# this should succeed (1980)
z = zipfile.ZipFile("ok.zip", "w")
zi = zipfile.ZipInfo("ok.txt", (1980, 1, 1, 0, 0, 0))
z.writestr(zi, "x")
z.close()
print("ok.zip created")
# this should raise ValueError (1979)
z = zipfile.ZipFile("bad.zip", "w")
zi = zipfile.ZipInfo("bad.txt", (1979, 12, 31, 23, 59, 59))
try:
z.writestr(zi, "x")
except Exception as e:
print("expected failure:", type(e).__name__, e)
If your modified formula fixes the build, the pip/hatchling step will proceed and the wheel will be created without the ZIP timestamp error.
Patch example for kosmorro.rb
Minimal patch to the formula (context only):
def install
- virtualenv_install_with_resources
+ # Ensure ZIP file timestamps are valid for DOS format (>= 1980-01-01 UTC)
+ ENV["SOURCE_DATE_EPOCH"] = [Time.now.to_i, 315532800].max.to_s
+
+ # Optional: avoid timezone conversion surprises (uncomment if needed)
+ # ENV["TZ"] = "UTC"
+
+ virtualenv_install_with_resources
end
Notes on committing:
- Update the formula in your tap and run
brew install --build-from-source kosmorrolocally. - If this is intended as a permanent reproducibility measure, consider leaving the clamp as-is; if you want the build timestamp to reflect build time (but still be safe), use the clamp expression shown above.
Sources
- ZipFile tests fail in negative timezone offsets on reproducible build environments (CPython issue)
- Reproducible Builds - Homebrew Documentation
- Support a SOURCE_DATE_EPOCH prior to 1980 (hatch PR)
- SOURCE_DATE_EPOCH - reproducible-builds.org
- Homebrew python.rb Formula - example of environment handling
Conclusion
The quickest, least invasive way to fix the “ZIP does not support timestamps before 1980” failure when installing termcolor with homebrew python is to ensure the build uses a legal timestamp: add ENV["SOURCE_DATE_EPOCH"] = [Time.now.to_i, 315532800].max.to_s (or "315532800") to kosmorro.rb before virtualenv_install_with_resources. That removes the python zipfile error during hatchling python wheel creation; upgrading to the hatchling release that clamps timestamps is the long-term upstream fix.