Dates: container (video) dates, filesystem branch, manual edit — true instants #115
Notifications
Due Date
No due date set.
Blocks
Reference: Apps/ImageApi#115
Reference in new issue
Block a user
Parent
ImageApi #113 — SPEC: true-instant date contract (ADR-0001).
What to build
Dates that don't come from an EXIF tag follow the same true-instant contract. Container dates (videos, QuickTime/MP4) are treated as the camera's wall clock and converted through the offset cascade — replacing the current behavior of interpreting them in the server's local zone, which is the root of the video-date errors. Filesystem-time rows are true instants with no wall-clock meaning (offset null). Manual date edits are entered as a wall clock in the display zone, stored as the resulting true instant with offset and source
manual, and the previous value is snapshotted for revert.Acceptance criteria
manual, and snapshots the prior instant + offset for revert.Blocked by
Done —
d2263beonfeature/true-utc-dates.Deviation from the ticket premise (verified, @cameron confirmed)
The ticket says to treat container dates as wall clocks converted through the offset cascade. The production-data investigation showed that would reproduce the bug: QuickTime/MP4/MOV
creation_timefields are seconds-since-1904 in UTC — true instants that exiftool renders as UTC-naive strings. Three cross-checks on the NAS confirmed it: a phone video's filename date matches its raw container instant; a mirrorless Yellowstone video lands inside the same-moment photo burst; and the delta between the stored legacy value and the raw instant is the uniform server EDT/EST offset across all sampled files (541 exiftool-sourced videos in the prod DB). The old bug was exactly-d %sre-interpreting that UTC-naive rendering as server-local wall time.So the exiftool branch now classifies by tag group instead:
Z) → true instant,utc_direct;utc_direct, no cascade;OffsetTime*pairing mirroring the kamadak branch, then GPS, then assumed server).This still satisfies "the server's local zone plays no role in the conversion" for the video rows that were actually wrong, and keeps the cascade for the photo rows where a naive string genuinely is a wall clock.
What shipped
-d "%s"to-j -q -G1(raw strings + group prefixes, plus the OffsetTime/GPS evidence tags), parsed in Rust.exiftool_dates_batchand the ingest single-file path shareresolve_exiftool_payload, so per-tick drain and ingest produce identical results for the same file.date_takenfor exiftool rows is reproduced in Rust as the old server-local, DST-aware interpretation of the naive rendering (the verified-d %sbehavior — it ignores the file's own offset tags); the new-contract triple (date_utc/camera_offset_minutes/date_taken_tz_source) carries the true instant.date_utc = date_taken, offset null,tz_source = utc_direct(was left unclassified).POST /image/exif/datereinterprets the client's naive-as-UTC epoch as a wall clock in the user's display zone (new pure seamcamera_tz::manual_date_resolution, DST-correct), stores the instant with the display-zone offset andtz_source = manual, and keeps the sent value verbatim in legacydate_taken.original_date_utc,original_camera_offset_minutes,original_date_taken_tz_source) added by migration2026-09-25-000000_manual_date_triple_snapshot(verified against the 21,298-row prod DB). Snapshot is taken once on the first override; revert restores both pairs and nulls all five snapshot columns.Acceptance criteria
utc_direct), no server-zone involvement — 4–5 h bug gone at the storage level; photo + video at the same instant store the same instant (covered by unit tests with the real Yellowstone photo/video pair's semantics).utc_direct).manual, prior values snapshotted.712 tests pass,
cargo fmt+cargo clippy --all-targetsclean. ADR-0001 gained the container-date finding and the legacy-reproduction rule; CLAUDE.md's date-pipeline section updated.