Dates: true_instant on metadata responses for instant matching
The mobile viewer anchored its "messages around this photo" window on the legacy date_taken (a wall clock stored as UTC for EXIF rows), while SMS-API dates are true instants: the window was off by the photo's UTC offset (4-5 h for photos taken at home; ImageApi #113 user story 3). Verified SMS-API side: importers store backup epochs as UTC, and 40/40 sampled rows equal their source epochs exactly. `/image/metadata` (+ GPS / date mutation responses) now carries `true_instant` from `date_view::file_true_instant`: `row_true_instant`, else the filename-class instant, else the earliest fs time. Server-side for the same reason as `wall_clock`: the drain's permanent floor rows have no `date_utc`. `set_capture_dates` fills both fields at all four response sites. dev/e2e: the manifest records each fixture's expected instant (the wall read in its GPS zone, else America/New_York) and `e2e.sh check` asserts it (97/97). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bc41c737e5
commit
9269b5c9fd
5 files changed
+117
-18
No files matched your search
@@ -242,6 +242,26 @@ pub fn file_wall_clock(
|
||||
wall.map(format_wall_clock)
|
||||
}
|
||||
|
||||
/// True instant for a file that may have no `image_exif` row yet — the
|
||||
/// [`file_wall_clock`] precedence (row, then filename-class date, then
|
||||
/// earliest fs time). The anchor for instant-to-instant matching (the
|
||||
/// "messages around this photo" window), never for display.
|
||||
pub fn file_true_instant(
|
||||
row: Option<&ImageExif>,
|
||||
rel_path: &str,
|
||||
fs_earliest: Option<i64>,
|
||||
display_zone: Tz,
|
||||
server_zone: Option<Tz>,
|
||||
) -> Option<i64> {
|
||||
if let Some(instant) = row.and_then(|r| row_true_instant(r, display_zone, server_zone)) {
|
||||
return Some(instant);
|
||||
}
|
||||
match crate::memories::extract_date_from_filename(rel_path) {
|
||||
Some(fd) => filename_true_instant(&fd, server_zone),
|
||||
None => fs_earliest,
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a unix instant as a naive wall in `zone`.
|
||||
fn render_in_zone_opt(instant: i64, zone: Tz) -> Option<NaiveDateTime> {
|
||||
zone.timestamp_opt(instant, 0)
|
||||
@@ -720,4 +740,43 @@ mod tests {
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_true_instant_uses_the_row_then_filename_then_fs_time() {
|
||||
// Row: the converted instant, not the wall (Tokyo 14:00 = 05:00Z).
|
||||
let r = row(
|
||||
"otd.jpg",
|
||||
Some(utc_epoch(2018, 9, 29, 14, 0)),
|
||||
Some("exif"),
|
||||
Some(utc_epoch(2018, 9, 29, 5, 0)),
|
||||
Some(540),
|
||||
);
|
||||
assert_eq!(
|
||||
file_true_instant(Some(&r), "otd.jpg", Some(0), ny(), Some(ny())),
|
||||
Some(utc_epoch(2018, 9, 29, 5, 0))
|
||||
);
|
||||
// No row: a wall-pattern filename is read in the server zone
|
||||
// (10:15 EDT = 14:15Z).
|
||||
assert_eq!(
|
||||
file_true_instant(
|
||||
None,
|
||||
"Screenshots/Screenshot_20220315-101500.png",
|
||||
Some(0),
|
||||
ny(),
|
||||
Some(ny())
|
||||
),
|
||||
Some(utc_epoch(2022, 3, 15, 14, 15))
|
||||
);
|
||||
// No row, no filename date: fs time is already an instant.
|
||||
assert_eq!(
|
||||
file_true_instant(
|
||||
None,
|
||||
"Undated/scan.jpg",
|
||||
Some(1_425_229_200),
|
||||
ny(),
|
||||
Some(ny())
|
||||
),
|
||||
Some(1_425_229_200)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user