feat(dates): photo date filters on the true instant (#152)
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>
This commit is contained in:
1 parent
9e780774d9
commit
19fbf43d40
12 files changed
+478
-90
No files matched your search
+6
-19
@@ -195,23 +195,9 @@ pub fn memories_anchor(
|
||||
.date_naive();
|
||||
return (now.timestamp(), today);
|
||||
};
|
||||
// End of D = start of D+1 minus a second. Midnight can fall in a DST
|
||||
// gap in a few zones; fall back to 01:00, then to a UTC reading.
|
||||
// End of D = start of D+1 minus a second.
|
||||
let next = d.succ_opt().unwrap_or(d);
|
||||
let start_of_next = [0, 1]
|
||||
.iter()
|
||||
.find_map(|h| {
|
||||
display_zone
|
||||
.from_local_datetime(&next.and_hms_opt(*h, 0, 0).expect("valid hour"))
|
||||
.earliest()
|
||||
})
|
||||
.map(|dt| dt.timestamp())
|
||||
.unwrap_or_else(|| {
|
||||
next.and_hms_opt(0, 0, 0)
|
||||
.expect("midnight")
|
||||
.and_utc()
|
||||
.timestamp()
|
||||
});
|
||||
let start_of_next = crate::date_view::day_start_instant(next, display_zone);
|
||||
(start_of_next - 1, d)
|
||||
}
|
||||
|
||||
@@ -233,13 +219,14 @@ pub fn parse_reference_date(raw: Option<&str>) -> Result<Option<NaiveDate>, Stri
|
||||
/// or past-year rows are dropped before [`day_in_span`] gets to match
|
||||
/// them. Converted rows are tested on `date_utc` (the true instant);
|
||||
/// unconverted rows on legacy `date_taken`, which sits up to ~15 h off
|
||||
/// the instant under the per-source conventions (wall↔instant confusion
|
||||
/// ≤ 12 h, zone range ≤ 2 h, DST margin 1 h) — hence the ±15 h band.
|
||||
/// the instant under the per-source conventions — hence the ±15 h band
|
||||
/// ([`crate::date_view::LEGACY_INSTANT_BAND_SECS`]).
|
||||
/// By construction the band subsumes the converted window, so the DAO's
|
||||
/// single outer bound on `date_taken` never drops a converted row.
|
||||
pub fn memories_span_windows(now_ts: i64, years_back: i64) -> (i64, i64, i64, i64) {
|
||||
let years_lo = now_ts - years_back * 365 * 86_400;
|
||||
(years_lo, now_ts, years_lo - 15 * 3600, now_ts + 15 * 3600)
|
||||
let band = crate::date_view::LEGACY_INSTANT_BAND_SECS;
|
||||
(years_lo, now_ts, years_lo - band, now_ts + band)
|
||||
}
|
||||
|
||||
/// True if `display_day` falls in the span (day/week/month) containing
|
||||
|
||||
Reference in new issue
Block a user