Dates: foundation — schema, EXIF branch re-base, offset cascade, display zone #114

Closed
opened 2026-09-24 22:52:46 +00:00 by cameron · 1 comment
Owner

Parent

ImageApi #113 — SPEC: true-instant date contract (ADR-0001).

What to build

The system stores, per photo, the true instant alongside the camera offset and an audit of how it was resolved — and the display zone becomes a zone name. New and re-ingested files get the new date fields populated at ingest; the legacy date column stays byte-identical; the metadata and /photos responses carry the new fields; the per-user preference carries an IANA zone name whose minute offset is derived DST-correct for the relevant year (replacing the fixed minute value as the source of truth).

Concretely: the offset cascade (explicit EXIF offset tag → GPS position through a timezone database for the capture year → server zone, recorded as an assumption) exists as a testable pure function; the waterfall's EXIF branch emits (true instant, camera offset, offset-source) instead of a mixed-convention epoch; the schema migration adds the nullable columns; the zone-name preference lands in the prefs surface with the old minute value kept as a derived/legacy read where clients still use it.

Acceptance criteria

  • Migration adds the new columns (instant, offset, offset-source) and the zone-name preference, all backward-compatible; legacy date column untouched.
  • Ingesting a photo with an EXIF offset tag stores the true instant (tag wall clock converted with the tag's offset), offset = the tag's, source = offset_tag.
  • Ingesting a photo with GPS but no offset tag stores the true instant converted with the GPS-resolved zone for the capture year (a summer and a winter photo at the same coordinates yield different offsets), source = gps.
  • Ingesting a photo with no zone evidence uses the server zone and records source = assumed_server.
  • The legacy date column is byte-identical for pre-existing rows and for newly ingested rows (computed as before).
  • Metadata and /photos responses include the new fields for rows that have them (null for unconverted rows).
  • Setting the display zone via the prefs surface is accepted; deriving the offset for a given instant respects DST (e.g. America/New_York gives -240 for July, -300 for January).
  • Waterfall tests: cascade precedence (tag beats GPS beats assumed), DST correctness, and the emitted audit values — at the existing pure-function seam.

Blocked by

None (can start immediately).

## Parent ImageApi #113 — SPEC: true-instant date contract (ADR-0001). ## What to build The system stores, per photo, the true instant alongside the camera offset and an audit of how it was resolved — and the display zone becomes a zone name. New and re-ingested files get the new date fields populated at ingest; the legacy date column stays byte-identical; the metadata and `/photos` responses carry the new fields; the per-user preference carries an IANA zone name whose minute offset is derived DST-correct for the relevant year (replacing the fixed minute value as the source of truth). Concretely: the offset cascade (explicit EXIF offset tag → GPS position through a timezone database for the capture year → server zone, recorded as an assumption) exists as a testable pure function; the waterfall's EXIF branch emits (true instant, camera offset, offset-source) instead of a mixed-convention epoch; the schema migration adds the nullable columns; the zone-name preference lands in the prefs surface with the old minute value kept as a derived/legacy read where clients still use it. ## Acceptance criteria - [ ] Migration adds the new columns (instant, offset, offset-source) and the zone-name preference, all backward-compatible; legacy date column untouched. - [ ] Ingesting a photo with an EXIF offset tag stores the true instant (tag wall clock converted with the tag's offset), offset = the tag's, source = `offset_tag`. - [ ] Ingesting a photo with GPS but no offset tag stores the true instant converted with the GPS-resolved zone **for the capture year** (a summer and a winter photo at the same coordinates yield different offsets), source = `gps`. - [ ] Ingesting a photo with no zone evidence uses the server zone and records source = `assumed_server`. - [ ] The legacy date column is byte-identical for pre-existing rows and for newly ingested rows (computed as before). - [ ] Metadata and `/photos` responses include the new fields for rows that have them (null for unconverted rows). - [ ] Setting the display zone via the prefs surface is accepted; deriving the offset for a given instant respects DST (e.g. `America/New_York` gives -240 for July, -300 for January). - [ ] Waterfall tests: cascade precedence (tag beats GPS beats assumed), DST correctness, and the emitted audit values — at the existing pure-function seam. ## Blocked by None (can start immediately).
Author
Owner

Implemented on feature/true-utc-dates (commit 2f2107d, local — not yet pushed).

  • Offset cascade as pure function in src/camera_tz.rs (tag -> GPS via bundled TZF geometry -> server zone), DST-correct for the capture date; 16 unit tests.
  • EXIF branch emits (date_utc, camera_offset_minutes, date_taken_tz_source): exif -> cascade; exiftool/fs_time -> utc_direct; filename -> unclassified (pending #116). Legacy date_taken byte-identical (wall-and_utc round-trip; migration touches no existing rows).
  • Migration 2026-09-24-000000_add_true_utc_dates adds the 3 image_exif columns + user_ai_prefs.display_tz (nullable, reversible).
  • DAO backfill_date_taken persists the triple; watcher/upload/set_image_gps feed the cascade; ExifMetadata + ExifSummary expose the fields (contract tests pin wire names).
  • Prefs: display_tz captured via reel request; DST helper verified (NY -240 July / -300 Jan). Nightly read path intentionally untouched (#119/#120).

Build + clippy clean; 625 lib + 702 bin tests pass (one pre-existing doctest failure in utils.rs at the base commit). Two-axis review: Standards 5 judgement-call findings (data clump deferred to #118), Spec 0 gaps.

Implemented on feature/true-utc-dates (commit 2f2107d, local — not yet pushed). - Offset cascade as pure function in src/camera_tz.rs (tag -> GPS via bundled TZF geometry -> server zone), DST-correct for the capture date; 16 unit tests. - EXIF branch emits (date_utc, camera_offset_minutes, date_taken_tz_source): exif -> cascade; exiftool/fs_time -> utc_direct; filename -> unclassified (pending #116). Legacy date_taken byte-identical (wall-and_utc round-trip; migration touches no existing rows). - Migration 2026-09-24-000000_add_true_utc_dates adds the 3 image_exif columns + user_ai_prefs.display_tz (nullable, reversible). - DAO backfill_date_taken persists the triple; watcher/upload/set_image_gps feed the cascade; ExifMetadata + ExifSummary expose the fields (contract tests pin wire names). - Prefs: display_tz captured via reel request; DST helper verified (NY -240 July / -300 Jan). Nightly read path intentionally untouched (#119/#120). Build + clippy clean; 625 lib + 702 bin tests pass (one pre-existing doctest failure in utils.rs at the base commit). Two-axis review: Standards 5 judgement-call findings (data clump deferred to #118), Spec 0 gaps.
cameron added the wayfinder:task label 2026-09-30 00:47:14 +00:00
Sign in to join this conversation.