fix(memories): sort Month span by time within a day
The Month sort keyed on the display day only. The sort is stable, so a day's photos kept DB order, merged across libraries, and their times jumped back and forth. Month now sorts like Week: full date, then timestamp (undated items last). The sort moves into sort_memories so it can be tested directly; the new test covers a multi-library, out-of-order day. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fd9ecbe53b
commit
4feffb62bb
1 file changed
+67
-25
+67
-25
@@ -660,35 +660,34 @@ pub fn gather_memory_items(
|
||||
|
||||
// Sort once over the merged result set. The SQL filter handles the
|
||||
// matching; sort order is purely UI concern.
|
||||
match span_mode {
|
||||
// Month: chronological — gives an "overview" feel.
|
||||
MemoriesSpan::Month => memories_with_dates.sort_by(|a, b| a.1.cmp(&b.1)),
|
||||
// Week: full date then timestamp (oldest → newest).
|
||||
MemoriesSpan::Week => {
|
||||
memories_with_dates.sort_by(|a, b| {
|
||||
a.1.cmp(&b.1)
|
||||
.then_with(|| match (a.0.created, b.0.created) {
|
||||
(Some(at), Some(bt)) => at.cmp(&bt),
|
||||
(Some(_), None) => std::cmp::Ordering::Less,
|
||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
||||
(None, None) => std::cmp::Ordering::Equal,
|
||||
})
|
||||
});
|
||||
}
|
||||
// Day: same calendar day across years, sub-sorted by timestamp.
|
||||
MemoriesSpan::Day => {
|
||||
memories_with_dates.sort_by(|a, b| match (a.0.created, b.0.created) {
|
||||
(Some(at), Some(bt)) => at.cmp(&bt),
|
||||
(Some(_), None) => std::cmp::Ordering::Less,
|
||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
||||
(None, None) => std::cmp::Ordering::Equal,
|
||||
});
|
||||
}
|
||||
}
|
||||
sort_memories(&mut memories_with_dates, span_mode);
|
||||
|
||||
Ok(memories_with_dates.into_iter().map(|(m, _)| m).collect())
|
||||
}
|
||||
|
||||
/// Order `(item, display_day)` pairs for the given span, oldest first.
|
||||
///
|
||||
/// Rows arrive in DB order, merged across libraries, so every span needs a
|
||||
/// timestamp key within a day — without one, a day's photos keep that
|
||||
/// arbitrary order and their times jump back and forth.
|
||||
fn sort_memories(memories: &mut [(MemoryItem, NaiveDate)], span: MemoriesSpan) {
|
||||
// Timestamped items before ones without a date.
|
||||
let by_created = |a: &MemoryItem, b: &MemoryItem| match (a.created, b.created) {
|
||||
(Some(at), Some(bt)) => at.cmp(&bt),
|
||||
(Some(_), None) => std::cmp::Ordering::Less,
|
||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
||||
(None, None) => std::cmp::Ordering::Equal,
|
||||
};
|
||||
match span {
|
||||
// Month / Week: full date then timestamp (oldest → newest).
|
||||
MemoriesSpan::Month | MemoriesSpan::Week => {
|
||||
memories.sort_by(|a, b| a.1.cmp(&b.1).then_with(|| by_created(&a.0, &b.0)));
|
||||
}
|
||||
// Day: same calendar day across years, sorted by timestamp.
|
||||
MemoriesSpan::Day => memories.sort_by(|a, b| by_created(&a.0, &b.0)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1195,6 +1194,49 @@ mod tests {
|
||||
assert_eq!(memories_with_dates[2].0.created.unwrap(), jan_16_2021_8am);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn month_span_sorts_by_time_within_a_day() {
|
||||
// Same display day, rows in DB order (merged across libraries) —
|
||||
// the Month sort used to keep this order, so times jumped around.
|
||||
let day = NaiveDate::from_ymd_opt(2019, 9, 14).unwrap();
|
||||
let at = |h: u32| day.and_hms_opt(h, 0, 0).unwrap().and_utc().timestamp();
|
||||
let item = |path: &str, created: Option<i64>, library_id: i32| MemoryItem {
|
||||
path: path.to_string(),
|
||||
created,
|
||||
modified: created,
|
||||
library_id,
|
||||
date_utc: created,
|
||||
camera_offset_minutes: None,
|
||||
date_taken_tz_source: None,
|
||||
wall_clock: None,
|
||||
};
|
||||
let earlier_day = NaiveDate::from_ymd_opt(2019, 9, 2).unwrap();
|
||||
let mut memories = vec![
|
||||
(item("lib1-15h.jpg", Some(at(15)), 1), day),
|
||||
(item("undated.jpg", None, 1), day),
|
||||
(item("lib2-09h.jpg", Some(at(9)), 2), day),
|
||||
(item("lib1-12h.jpg", Some(at(12)), 1), day),
|
||||
(
|
||||
item("sept-2nd.jpg", Some(at(20) - 12 * 86_400), 2),
|
||||
earlier_day,
|
||||
),
|
||||
];
|
||||
|
||||
sort_memories(&mut memories, MemoriesSpan::Month);
|
||||
|
||||
let paths: Vec<&str> = memories.iter().map(|(m, _)| m.path.as_str()).collect();
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
"sept-2nd.jpg",
|
||||
"lib2-09h.jpg",
|
||||
"lib1-12h.jpg",
|
||||
"lib1-15h.jpg",
|
||||
"undated.jpg"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// ---- Day-matching (ticket #119) ----
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user