Switch read paths to the true-instant date contract (ticket #119)
- New src/date_view.rs: pure read-side functions (row_true_instant, row_display_wall, row_display_day, filename_true_instant) applying the per-source convention table for unconverted rows and the new-contract triple for converted rows (ADR-0001). - /memories: grouping on the row's true instant rendered in the owner's display zone (prefs -> app default), not the client device offset. SQL is a coarse interval pre-filter over the full years-back window; exact month-day/week/month matching across past years happens in Rust (memories::day_in_span, sqlite_w_week). - Reels: ReelSelector::Memories carries an IANA display_tz instead of a fixed minute offset; pre-gen params read REEL_PREGEN_DISPLAY_TZ; the tz_offset_minutes ledger column stores the zone offset at render time. - Insights: prompt-facing dates render as the display wall; the proximity timestamp and chat context block use the true instant; InsightGenerator reads the owner's display zone from prefs per call. - GpsPhotoSummary carries the new-contract triple alongside the frozen legacy date_taken. Closes #119
This commit is contained in:
1 parent
cd375f18db
commit
af87765bbb
13 files changed
+1590
-301
No files matched your search
+34
-18
@@ -9,9 +9,10 @@
|
||||
//! runs in the background job.
|
||||
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use chrono::{DateTime, Datelike, FixedOffset};
|
||||
use chrono::{Datelike, TimeZone};
|
||||
|
||||
use super::{PlannedBeat, ReelMeta, SegmentMedia};
|
||||
use crate::database::{ExifDao, InsightDao};
|
||||
@@ -64,7 +65,10 @@ pub fn budget_segments(requested_max: usize) -> usize {
|
||||
pub enum ReelSelector {
|
||||
Memories {
|
||||
span: MemoriesSpan,
|
||||
tz_offset_minutes: i32,
|
||||
/// Owner's display zone as an IANA name (ADR-0001) — grouping is
|
||||
/// zone-anchored, not device-offset-anchored, so DST shifts are
|
||||
/// absorbed instead of silently re-bucketing the reel.
|
||||
display_tz: String,
|
||||
library: Option<String>,
|
||||
max_segments: usize,
|
||||
},
|
||||
@@ -78,13 +82,13 @@ impl ReelSelector {
|
||||
match self {
|
||||
ReelSelector::Memories {
|
||||
span,
|
||||
tz_offset_minutes,
|
||||
display_tz,
|
||||
library,
|
||||
max_segments,
|
||||
} => format!(
|
||||
"memories:span={:?}:tz={}:lib={}:max={}",
|
||||
span,
|
||||
tz_offset_minutes,
|
||||
display_tz,
|
||||
library.as_deref().unwrap_or("all"),
|
||||
max_segments
|
||||
),
|
||||
@@ -281,24 +285,24 @@ pub fn resolve(
|
||||
match selector {
|
||||
ReelSelector::Memories {
|
||||
span,
|
||||
tz_offset_minutes,
|
||||
display_tz,
|
||||
library,
|
||||
max_segments,
|
||||
} => {
|
||||
let client_tz = FixedOffset::east_opt(tz_offset_minutes * 60);
|
||||
let zone =
|
||||
chrono_tz::Tz::from_str(display_tz).unwrap_or(chrono_tz::Tz::America__New_York);
|
||||
let items = memories::gather_memory_items(
|
||||
app_state,
|
||||
exif_dao,
|
||||
span_context,
|
||||
*span,
|
||||
*tz_offset_minutes,
|
||||
client_tz,
|
||||
zone,
|
||||
library.as_deref(),
|
||||
)?;
|
||||
|
||||
// Split into photos and video clips; anything that's neither is
|
||||
// dropped. Years span both, computed before the budget narrows it.
|
||||
let years = distinct_years(&items, client_tz);
|
||||
let years = distinct_years(&items, zone);
|
||||
let meta = ReelMeta { span: *span, years };
|
||||
|
||||
let (photos, videos): (Vec<_>, Vec<_>) = items
|
||||
@@ -321,15 +325,12 @@ pub fn resolve(
|
||||
|
||||
/// Distinct calendar years represented by the selected media, in the client's
|
||||
/// timezone, ascending. Used to tell the scripter how far back the reel reaches.
|
||||
fn distinct_years(items: &[memories::MemoryItem], tz: Option<FixedOffset>) -> Vec<i32> {
|
||||
fn distinct_years(items: &[memories::MemoryItem], zone: chrono_tz::Tz) -> Vec<i32> {
|
||||
let mut years: Vec<i32> = items
|
||||
.iter()
|
||||
.filter_map(|it| it.created)
|
||||
.filter_map(|ts| DateTime::from_timestamp(ts, 0))
|
||||
.map(|dt| match tz {
|
||||
Some(off) => dt.with_timezone(&off).year(),
|
||||
None => dt.year(),
|
||||
})
|
||||
.filter_map(|ts| zone.timestamp_opt(ts, 0).single())
|
||||
.map(|dt| dt.year())
|
||||
.collect();
|
||||
years.sort_unstable();
|
||||
years.dedup();
|
||||
@@ -406,13 +407,13 @@ mod tests {
|
||||
fn descriptor_is_stable_and_distinguishes_inputs() {
|
||||
let a = ReelSelector::Memories {
|
||||
span: MemoriesSpan::Day,
|
||||
tz_offset_minutes: -480,
|
||||
display_tz: "America/New_York".to_string(),
|
||||
library: None,
|
||||
max_segments: 24,
|
||||
};
|
||||
let b = ReelSelector::Memories {
|
||||
span: MemoriesSpan::Week,
|
||||
tz_offset_minutes: -480,
|
||||
display_tz: "America/New_York".to_string(),
|
||||
library: None,
|
||||
max_segments: 24,
|
||||
};
|
||||
@@ -429,21 +430,33 @@ mod tests {
|
||||
created: Some(1_560_000_000), // 2019
|
||||
modified: None,
|
||||
library_id: 1,
|
||||
date_utc: None,
|
||||
camera_offset_minutes: None,
|
||||
date_taken_tz_source: None,
|
||||
},
|
||||
memories::MemoryItem {
|
||||
path: "b".into(),
|
||||
created: Some(1_560_086_400), // 2019
|
||||
modified: None,
|
||||
library_id: 1,
|
||||
date_utc: None,
|
||||
camera_offset_minutes: None,
|
||||
date_taken_tz_source: None,
|
||||
},
|
||||
memories::MemoryItem {
|
||||
path: "c".into(),
|
||||
created: Some(1_623_000_000), // 2021
|
||||
modified: None,
|
||||
library_id: 1,
|
||||
date_utc: None,
|
||||
camera_offset_minutes: None,
|
||||
date_taken_tz_source: None,
|
||||
},
|
||||
];
|
||||
assert_eq!(distinct_years(&items, None), vec![2019, 2021]);
|
||||
assert_eq!(
|
||||
distinct_years(&items, chrono_tz::Tz::America__New_York),
|
||||
vec![2019, 2021]
|
||||
);
|
||||
}
|
||||
|
||||
// Build an item at a given unix timestamp (seconds) with a chosen extension.
|
||||
@@ -453,6 +466,9 @@ mod tests {
|
||||
created: Some(ts),
|
||||
modified: None,
|
||||
library_id: 1,
|
||||
date_utc: None,
|
||||
camera_offset_minutes: None,
|
||||
date_taken_tz_source: None,
|
||||
}
|
||||
}
|
||||
fn item_at(ts: i64, name: &str) -> memories::MemoryItem {
|
||||
|
||||
Reference in new issue
Block a user