How to timestamp normaliser vs cli reproducible-build tools
- Step 1Decide if you need a script or a one-off — If timestamp canonicalisation must run inside CI on every build, a CLI step (
strip-nondeterminism, orSOURCE_DATE_EPOCHhonoured by your packer) belongs in the pipeline. For an ad-hoc, private, no-install normalisation, use the browser tool. - Step 2Open the browser normaliser for the one-off path — Go to /archive-tools/timestamp-normalizer. It runs client-side with
fflateandlibarchiveWASM — no account needed for files under the Free 50 MB / 500-entry cap. - Step 3Match the CLI's epoch with the date picker — CLI reproducible builds usually pin to the ZIP epoch. Leave the Target date on its
1980-01-01default to mirror that, or set it to the calendar date yourSOURCE_DATE_EPOCHresolves to (date-only — the tool stamps 00:00:00 UTC). - Step 4Run and download the normalised ZIP — The tool extracts every entry and re-zips with the uniform mtime. Output is
<name>-normalized.zip— note it is ZIP even if you dropped a tar.gz, where a CLItarflow would keep the tar.gz container. - Step 5Verify against the CLI's hash — If a teammate produced an archive via the CLI route and you produced one in the browser, compare hashes with checksum-generator. They'll match only if both used the same container, compression, and date — remember the browser tool re-compresses to ZIP level 6.
- Step 6Pick the right tool for the leftover differences — If hashes still differ after timestamps are pinned, the remaining non-determinism is usually file ordering, a wrapper folder, or path casing. Use path-prefix-remover and filename-sanitizer for those.
JAD Timestamp Normaliser vs CLI reproducible-build approaches
Comparison of the common ways to canonicalise archive timestamps. The browser tool wins on setup and privacy; the CLI wins on in-pipeline integration and keeping the original container.
| Capability | JAD Timestamp Normaliser | strip-nondeterminism | TZ=UTC zip -X / touch + re-zip |
|---|---|---|---|
| Install required | None (browser) | Perl + package | zip/tar binaries |
| Runs offline / private | Yes, in-browser | Yes, local | Yes, local |
| Reads 7z / RAR / ISO | Yes (libarchive WASM) | Limited | No (need 7z/tar) |
| Keeps original container | No — always ZIP | Yes (in place) | Yes |
| Fits in a CI script | Runner only (Pro+) | Yes, native | Yes, native |
| One uniform date for all entries | Yes | Yes | Yes (via touch) |
| Default date | 1980-01-01 (picker) | SOURCE_DATE_EPOCH / 1980 | Manual |
What each tool actually changes
Scope clarity. The JAD tool only rewrites timestamps and re-packs to ZIP — it does not repair, re-level, or convert to 7z.
| Concern | JAD Timestamp Normaliser | Notes |
|---|---|---|
| Entry modification time | Set to one chosen date | Every entry, no exceptions |
| File contents | Unchanged | Extracted and re-packed verbatim |
| Compression | Re-encoded to DEFLATE level 6 | Output size may differ from input |
| Output container | ZIP only | tar.gz / 7z in becomes .zip out |
| Corruption repair | Not done | Use corrupted-zip-repair sibling |
| Time of day | Always 00:00:00 UTC | Date-only picker |
When to use which
A quick decision guide.
| Situation | Best choice |
|---|---|
| One-off, proprietary artifact, no install | JAD Timestamp Normaliser (browser) |
| Every build in CI must be deterministic | strip-nondeterminism / SOURCE_DATE_EPOCH in the pipeline |
| Must keep a tar.gz container | CLI (browser tool outputs ZIP) |
| Need to read a 7z/RAR with no 7z installed | JAD Timestamp Normaliser |
| Pro+ team wanting to offload large jobs | JAD via @jadapps/runner |
Cookbook
Side-by-side equivalents so you can see the browser tool and the CLI doing the same canonicalisation.
The CLI way vs the browser way
Pinning all entry times to the ZIP epoch. Left: the CLI reproducible-build approach. Right: the same outcome in the browser tool with one click.
CLI (in a build script):
strip-nondeterminism --type zip dist.zip
# or: TZ=UTC find dist -exec touch -d @315532800 {} + ; zip -rX dist.zip dist
Browser (one-off):
drop dist.zip -> Target date 1980-01-01 -> dist-normalized.zipMatching a SOURCE_DATE_EPOCH date
Your pipeline sets SOURCE_DATE_EPOCH to a commit date. To produce a matching archive ad-hoc, resolve that epoch to a calendar date and set the picker.
SOURCE_DATE_EPOCH=1749513600 -> 2025-06-10 (UTC) Browser: Target date = 2025-06-10 Result: every entry mtime = 2025-06-10T00:00:00.000Z Caveat: the browser tool stamps 00:00:00 UTC; if the CLI used a non-midnight epoch second, the times won't match exactly even though the date does.
Reading a 7z without installing 7-Zip
A CLI touch + re-zip flow needs a 7z binary to open the archive first. The browser tool reads 7z directly via libarchive WASM, then re-packs to ZIP.
CLI: 7z x payload.7z -o tmp && touch ... && zip ... (needs 7z) Browser: drop payload.7z directly -> libarchive WASM reads it -> payload-normalized.zip with all mtimes = 1980-01-01
Why the hashes still differ after both pin the date
A common gotcha: timestamps match but hashes don't. The browser tool re-compresses to ZIP level 6 and outputs ZIP, so it won't match a CLI tar.gz or a store-level ZIP byte-for-byte.
CLI output: dist.tar.gz (gzip container, mtimes pinned) Browser output: dist-normalized.zip (DEFLATE level 6) Different container + codec -> different bytes -> different hash. They are equivalent in content, not in bytes. To compare bytes, use the SAME container on both sides.
Auditing a teammate's reproducible ZIP
You received a ZIP that should be reproducible. Normalise your local rebuild in the browser, then diff the two with the sibling tool to find the offending entry.
1. Build locally, normalise: mine-normalized.zip 2. /archive-tools/archive-diff: theirs.zip vs mine-normalized.zip -> reports added / removed / changed entries 3. Differences narrow to file ordering or one stray file, not timestamps (those are now identical).
Edge cases and what actually happens
It is not a ZIP-repair tool
Out of scopeDespite older comparisons naming zip -F or DiskInternals ZIP Repair, this tool does not repair structural corruption — it canonicalises timestamps. For a damaged central directory, use corrupted-zip-repair; for integrity checks, archive-integrity-tester.
Browser output won't byte-match a CLI tar.gz
ExpectedThe browser tool always outputs a DEFLATE ZIP. A CLI flow that keeps a tar.gz container will never produce the same bytes, even with identical timestamps. Compare like-for-like containers, or treat the two as content-equivalent rather than byte-equivalent.
Non-midnight SOURCE_DATE_EPOCH
Partial matchIf your pipeline pins to an exact epoch second (not midnight), the browser tool — which stamps 00:00:00 UTC of the chosen date — will match the date but not the second. For exact byte parity, the CLI must also be set to midnight UTC, or both must agree on the second, which the date-only picker cannot express.
No compression-level control
By designCLI tools let you choose store vs deflate vs different levels. The normaliser is fixed at DEFLATE level 6 and offers no level option, so it cannot reproduce a store-level (level 0) archive's bytes. Use a CLI or smart-archive-compressor when you need level control.
No in-product REST run endpoint
Browser onlyArchive tools have no public POST run API. Unlike a server CLI you can curl, this runs in the browser; Pro+ tiers can offload to the @jadapps/runner (a local headless Chromium session). For unattended CI you may still prefer a native CLI step.
Encrypted source archive
FailNo password field exists, so an encrypted ZIP fails to read. A CLI with the password could process it directly. Decrypt first via multi-format-extractor, then normalise.
Free tier 50 MB / 500-entry cap
Tier limitA CLI on your own machine has no such cap. The browser Free tier stops at 50 MB / 500 entries; Pro reaches 500 MB / 50,000, and Pro + Media / Developer reach 2 GB / 500,000. Large monorepo bundles may need a paid tier or the CLI.
Empty directories vanish
Not preservedSome CLI flows preserve empty-directory entries; the browser tool drops them because extraction keeps only file entries. If empty folders are load-bearing for your build, the CLI route is safer.
Frequently asked questions
Why compare against strip-nondeterminism, not zip -F?
Because zip -F and DiskInternals ZIP Repair fix structural corruption, which is a different job. The correct peers for timestamp canonicalisation are strip-nondeterminism, SOURCE_DATE_EPOCH-aware packers, and TZ=UTC zip -X / touch + re-zip. This tool sits alongside those.
Will the browser output exactly match my CLI build's hash?
Only if both sides use the same container, codec/level, file ordering, and date. The browser tool always emits a DEFLATE-level-6 ZIP, so it will not byte-match a tar.gz or a store-level ZIP even with identical timestamps. Match the container to compare bytes.
Can I set the exact epoch second like SOURCE_DATE_EPOCH?
No. The picker is date-only and applies 00:00:00 UTC. You can match the calendar date your epoch resolves to, but not a non-midnight second. For exact-second parity, use a CLI on both sides set to midnight UTC.
Does the browser tool need an install or account?
No install ever. No account for files under the Free 50 MB / 500-entry cap. It runs entirely client-side via fflate and libarchive WASM — that is its main advantage over the CLI toolchain.
Is it private — does my artifact leave my machine?
No content leaves your machine. Reading and re-packing happen in the browser; only an anonymous usage counter is recorded. That parity with a local CLI is the key reason to use it over arbitrary online ZIP services.
Can it read 7z and RAR like 7-Zip does?
It can read them (via libarchive WASM) but cannot write them. So you can drop a 7z or RAR and get a normalised ZIP, but you cannot produce a 7z/RAR output. A CLI is required if the output must stay 7z/RAR.
Can I run it in CI like a CLI?
Not via a REST endpoint — archive tools have no public run API. Pro+ tiers can offload jobs to the @jadapps/runner (a paired headless Chromium), but for fully unattended pipelines a native CLI step (strip-nondeterminism) is usually the cleaner fit.
Does it change compression like a CLI level flag?
It always re-compresses to DEFLATE level 6 with no level option. CLI tools let you pick store/levels. If you need to reproduce a specific compression, use a CLI or compression-level-optimizer.
What about file ordering — does that affect reproducibility?
Yes. Even with identical timestamps, different entry ordering yields different bytes. The browser tool re-packs in the order it reads entries; a CLI may sort. If hashes differ after pinning dates, ordering or a wrapper folder is the usual culprit.
Which is faster for a big archive?
For very large artifacts, a native CLI on a fast machine generally wins, and the browser Free tier caps at 50 MB anyway. For small-to-medium archives, the browser tool is effectively instant and saves the install/setup time entirely.
Can it repair a broken ZIP while normalising?
No. It expects a readable archive. For a corrupt central directory, run corrupted-zip-repair first, then normalise the repaired output.
What's the single biggest gotcha switching from CLI to browser?
The output container changes to ZIP. Teams expecting a tar.gz are surprised when the normalised file is a .zip. Convert afterwards with zip-to-tar-gz if the downstream consumer requires tar.gz.
Privacy first
Every JAD Archive tool runs entirely in your browser using fflate, @zip.js/zip.js, and the libarchive WASM bridge. Your archives never leave your device — verified by zero outbound network requests during processing.