Add -stc: store/restore the oldest of {atime, mtime, ctime} as creation time - #243
Open
amayer-glei wants to merge 2 commits into
Open
Add -stc: store/restore the oldest of {atime, mtime, ctime} as creation time#243amayer-glei wants to merge 2 commits into
-stc: store/restore the oldest of {atime, mtime, ctime} as creation time#243amayer-glei wants to merge 2 commits into
Conversation
Author
|
Why: Android>Windows 7z backups resets ctime to "now" 🫠 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Branch layout (same change, three packagings)
This PR contains only the code changes — branch
stc: 2 commits on top of26.00(CLI flag, GUI checkbox). The same change is also published on the fork in two other
packagings, so you can pick whatever suits your contribution workflow:
i18n—stc+ theLang/translation-pack update (id
4087, 325 files; also offered to the language-pack repo).gh—i18n+ GitHub Actions CI(matrix x86/x64/arm64,
-stcsmoke test) with a rolling prereleaseci-ghcarrying per-arch binaryzips, so the feature can be tried without building.
Summary
This adds a new option,
-stc, that remaps the creation time (ctime) slot to theoldest of a file's three timestamps —
min(atime, mtime, ctime)— both when storingfiles into an archive and when restoring them to the filesystem.
a/u): the value written into the archive's CTime field becomesmin(atime, mtime, ctime)of the source file (the real ctime is ignored as a value, but isstill included in the
min). atime/mtime slots are unaffected.x/e): the extracted file's creation time is set tomin(atime, mtime, ctime)of the archived timestamps (whichever are defined). atime/mtimeare restored normally.
The same single switch drives both directions (like
-ssp/-stl), and it is off bydefault, so behavior is unchanged unless
-stcis given.Why
A file's creation time is often newer than its real age (e.g. after a copy or a
checkout), while the oldest of its timestamps best reflects "how old is this data, really."
This option lets an archive preserve that oldest-known time as the creation time, and restore
it on extraction — a fixed, always-
min()special case of a possible future ffmpeg-style-map i:otimestamp remap (-mapis still free for that).Usage
Command line
-stcalso forces CTime to be stored (equivalent to-mtc), so there is always a ctimeslot to hold the oldest value. atime/mtime are stored per their usual defaults /
-mta/-mtm.GUI (7zFM / 7zG)
Add to archive→Options...→ Time group → new checkbox:The setting is persisted in the registry (next to "Do not change source files last access
time") and internally forces "Store creation time" when enabled.
Behavior details / edge cases
tctime option (e.g. zip/tar NTFS times). Tested with 7z.the archive; if only mtime was stored, creation time = mtime.
files get the remapped ctime.
"ctime" slot follows 7-Zip's existing convention (
st_ctime).Implementation
Both timestamp write-paths are remapped on compression, and a single choke point on extraction.
Shared / engine:
CPP/Windows/TimeUtils.h— newFiTime_Min3()helper (built on the existingCompare_FiTime).CPP/7zip/Common/FileStreams.{h,cpp}—CInFileStream::StoreOldestCTime; remap inGetProps(the solid/fast path, regular files) for Windows and Linux.CPP/7zip/UI/Common/UpdateCallback.{h,cpp}—CArchiveUpdateCallback::StoreOldestCTime;remap in
GetProperty/GetRootProp(the property path, used for directories); flag passed tothe input stream in
GetStream.CPP/7zip/UI/Common/Update.{h,cpp}—CUpdateOptions::StoreOldestCTime, wired to the callback.CPP/7zip/UI/Common/ArchiveExtractCallback.{h,cpp}—CExtractNtOptions::OldestCTime;GetFiTimesCAM()takes the flag and rewritesCTimeto the oldest defined time — one placecovering files, directories and symlinks.
Command line (
7zz/7za):CPP/7zip/UI/Common/ArchiveCommandLine.cpp—NKey::kOldestCTime+{ "stc", SWFRM_SIMPLE }in
kSwitchForms[](positional alignment preserved); Parse2 handling for the extract branch(
nt.OldestCTime) and the update branch (StoreOldestCTime+ injectedtcproperty to forcectime storage).
CPP/7zip/UI/Console/Main.cpp— help text line.GUI:
CPP/7zip/UI/GUI/CompressDialogRes.h— newIDX_COMPRESS_OLDEST_CTIME.CPP/7zip/UI/GUI/CompressOptionsDialog.rc— new checkbox; "Time" group box and dialog heightgrown by 20 px.
CPP/7zip/UI/GUI/CompressDialog.{h,cpp}—CInfo::OldestCTime/CCompressDialog::OldestCTimeand the
SET_GUI_BOOL/SET_FINAL_BOOL_PAIRS/ options-dialog wiring (mirrorsPreserveATime).CPP/7zip/UI/Common/ZipRegistry.{h,cpp}— registry persistence.CPP/7zip/UI/GUI/UpdateGUI.cpp— maps the checkbox toCUpdateOptions::StoreOldestCTimeandforces the
tcproperty.No changes to
-Wall -WXwere needed; no existing behavior is altered when the option is absent.Localization
Only the GUI checkbox string is translatable; the CLI help line in
Console/Main.cppis hardcoded English (the console
7zzhelp is not localized).The translatable string is dialog ID
IDX_COMPRESS_OLDEST_CTIME=4087, registered inkLangIDs_Options[], soLangSetDlgItems()substitutes it fromLang\<lang>.txtat runtime(falling back to the English RC caption when absent).
The
Lang/*.txttranslation files are not part of this source tree; they are maintained inthe separate language pack repo (
PopuriAO29/7zip-lang, 7-Zip 26.01) and feed the installer(
DOC/7zip.wxs). Upstream,4087was the next free id after the existing Time-group strings(
4082–4086) and beforesec/ns(4090/4091), so there is no id collision.The new string has been added at id
4087to the language pack (seeLang/andLang-updated.zip), inserted byte-exactly (UTF-8 BOM + CRLF preserved, one line added after the4086entry;4086/4090/4091verified unchanged):de, fr, es, it, pt, pt-br, nl, pl, cs, sk, sv, fi, ru, uk, ja, ko, zh-cn, zh-tw, tr, el, hu,
ro, bg, sl, ca, gl, id, ar, fa, bs, va.
en-*variants, theen.ttttemplate, andaz/co/rue(which have a translated Time group but were not machine-guessed — thesethree still need a native speaker).
the English RC caption for the whole group, including this checkbox).
English source (ID
4087):German (as applied to all
de-*files):CI
New workflow
.github/workflows/build.yml(the tree previously had no CI):x86/x64/arm64onwindows-latest(VS 2026 —windows-latestno longer ships VS2022, which was the first CI failure:
vswhere [17.0,18.0)finds nothing →vcvarsallpath empty →nmake9009), oneBuildBin.vs2026.cmd <arch>per leg,fail-fast: false.No 32-bit
armleg: the runner's VS2026 lacks the ARM32 VC++ component (vcvarsall amd64_armfails within seconds — second CI failure), and official 7-Zip ships x86/x64/arm64 only.
7zz a -stc→7zz l -sltassertsCreated == Modifiedon the oldest date(DST-proof on purpose: 7-Zip's listing converts via
FileTimeToLocalFileTime, which appliesthe current DST bias, so the absolute clock time in the listing is not stable across DST —
comparing the two listing lines against each other is), then
7zz x -stcasserts the extractedCreationTimeis exactly the oldest timestamp, plus7zz t.7zip-<arch>upload of every bundle binary(
7z.dll,7zz.exe,7za.exe,7zr.exe,7zFM.exe,lzma.exe, the codec DLLs, SFX modules).On pushes, a
releasejob additionally republishes a rolling prereleaseci-<branch>with per-arch zips (
gh release,contents: writescoped to that job only) — so thebinaries are one click away on the fork's Releases page.
BuildBin.*.cmddoes not propagate an nmake failure (popdresetsERRORLEVEL), so the workflow explicitly verifies key outputs exist after the build(
7z.dll,7zz.exe) and usesif-no-files-found: erroron upload as a second net.Validated locally with Docker:
rhysd/actionlint→ 0 errors; both PowerShell steps weredry-run against the real x64 build outputs (smoke test passes, 13 artifacts collected).
Testing
Build (clean, all bundles, warnings-as-errors):
→
7zz.exe,7za.exe,7z.dll,7zFM.exe,lzma.exe, SFX modules all build with zeroerrors/warnings.
Smoke tests (source file set to Creation=
2020-01-03, Access=2020-01-02, Write=2020-01-01← oldest):
7zz a -stc→7zz l -sltCreated = 2020-01-01(oldest), not real ctime2020-01-03✅-mtc -mta -mtm(no remap), then7zz x -stcCreationTime = 2020-01-01(oldest) ✅7zz x(no-stc)CreationTime = 2020-01-03(stored ctime) — default unchanged ✅