feat(dates): photo date filters on the true instant (#152) #162

Merged
cameron merged 1 commits from feature/true-instant-date-filters into master 2026-10-11 02:04:09 +00:00
Owner

date_from / date_to on /photos, /photos/exif and unified search ran on
the legacy mixed-convention date_taken, so a photo could display inside
a range yet be filtered out of it by up to its UTC offset (or by years,
for rows the filename class re-dated).

The filter lives in the shared ExifDao::query_by_exif, so it's fixed
once there:

  • query_by_exif takes Option<date_view::InstantRange> (bounds + display
    and server zones) instead of raw date_from / date_to.
  • SQL pre-filter: converted rows on date_utc exactly; unconverted rows
    (drain pending, permanent floor) on date_taken widened by
    LEGACY_INSTANT_BAND_SECS (±15 h, now shared with /memories). Converted
    rows are deliberately not bounded by date_taken: a re-dated row's
    legacy value can sit years from its instant.
  • Exact pass in Rust via InstantRange::contains → row_true_instant, so
    every returned row's true instant is inside the range.

Calendar days now mean the owner's days: nl_query::iso_to_unix builds
day bounds in the display zone (it used UTC midnights, which only lined
up with the legacy wall-as-UTC column), and unified search's "today" is
the owner's today rather than UTC's. The midnight-with-DST-gap fallback
moves from memories_anchor into date_view::day_start_instant.

Compatibility: clients already sending real instants (Apollo's
/photos/exif windows) get correct edges with no change. Clients sending
UTC-midnight day bounds see ranges shift by up to the UTC offset until
they send display-zone bounds.

No index on date_utc; the OR clause scans, which is milliseconds at
personal-library scale (same as /memories today).

dev/e2e check gains /photos/exif wall_clock + true_instant assertions and
range-edge checks ([t, t] in, [.., t-1] and [t+1, ..] out). Not run here:
the harness needs the WSL + Windows toolchain.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com

date_from / date_to on /photos, /photos/exif and unified search ran on the legacy mixed-convention date_taken, so a photo could display inside a range yet be filtered out of it by up to its UTC offset (or by years, for rows the filename class re-dated). The filter lives in the shared ExifDao::query_by_exif, so it's fixed once there: - query_by_exif takes Option<date_view::InstantRange> (bounds + display and server zones) instead of raw date_from / date_to. - SQL pre-filter: converted rows on date_utc exactly; unconverted rows (drain pending, permanent floor) on date_taken widened by LEGACY_INSTANT_BAND_SECS (±15 h, now shared with /memories). Converted rows are deliberately not bounded by date_taken: a re-dated row's legacy value can sit years from its instant. - Exact pass in Rust via InstantRange::contains → row_true_instant, so every returned row's true instant is inside the range. Calendar days now mean the owner's days: nl_query::iso_to_unix builds day bounds in the display zone (it used UTC midnights, which only lined up with the legacy wall-as-UTC column), and unified search's "today" is the owner's today rather than UTC's. The midnight-with-DST-gap fallback moves from memories_anchor into date_view::day_start_instant. Compatibility: clients already sending real instants (Apollo's /photos/exif windows) get correct edges with no change. Clients sending UTC-midnight day bounds see ranges shift by up to the UTC offset until they send display-zone bounds. No index on date_utc; the OR clause scans, which is milliseconds at personal-library scale (same as /memories today). dev/e2e check gains /photos/exif wall_clock + true_instant assertions and range-edge checks ([t, t] in, [.., t-1] and [t+1, ..] out). Not run here: the harness needs the WSL + Windows toolchain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cameron added 1 commit 2026-10-11 02:03:53 +00:00
date_from / date_to on /photos, /photos/exif and unified search ran on
the legacy mixed-convention date_taken, so a photo could display inside
a range yet be filtered out of it by up to its UTC offset (or by years,
for rows the filename class re-dated).

The filter lives in the shared ExifDao::query_by_exif, so it's fixed
once there:

- query_by_exif takes Option<date_view::InstantRange> (bounds + display
  and server zones) instead of raw date_from / date_to.
- SQL pre-filter: converted rows on date_utc exactly; unconverted rows
  (drain pending, permanent floor) on date_taken widened by
  LEGACY_INSTANT_BAND_SECS (±15 h, now shared with /memories). Converted
  rows are deliberately not bounded by date_taken: a re-dated row's
  legacy value can sit years from its instant.
- Exact pass in Rust via InstantRange::contains → row_true_instant, so
  every returned row's true instant is inside the range.

Calendar days now mean the owner's days: nl_query::iso_to_unix builds
day bounds in the display zone (it used UTC midnights, which only lined
up with the legacy wall-as-UTC column), and unified search's "today" is
the owner's today rather than UTC's. The midnight-with-DST-gap fallback
moves from memories_anchor into date_view::day_start_instant.

Compatibility: clients already sending real instants (Apollo's
/photos/exif windows) get correct edges with no change. Clients sending
UTC-midnight day bounds see ranges shift by up to the UTC offset until
they send display-zone bounds.

No index on date_utc; the OR clause scans, which is milliseconds at
personal-library scale (same as /memories today).

dev/e2e check gains /photos/exif wall_clock + true_instant assertions and
range-edge checks ([t, t] in, [.., t-1] and [t+1, ..] out). Not run here:
the harness needs the WSL + Windows toolchain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cameron merged commit d50cad7421 into master 2026-10-11 02:04:09 +00:00
cameron deleted branch feature/true-instant-date-filters 2026-10-11 02:04:09 +00:00
Sign in to join this conversation.