diff --git a/src/memories.rs b/src/memories.rs index c50439b..2e46328 100644 --- a/src/memories.rs +++ b/src/memories.rs @@ -1,9 +1,7 @@ use actix_web::web::Data; use actix_web::{HttpRequest, HttpResponse, Responder, get, web}; use chrono::LocalResult::{Ambiguous, Single}; -use chrono::{ - DateTime, Datelike, FixedOffset, Local, LocalResult, NaiveDate, TimeZone, Timelike, Utc, -}; +use chrono::{DateTime, Datelike, FixedOffset, Local, LocalResult, NaiveDate, TimeZone, Utc}; use log::{debug, trace, warn}; use opentelemetry::KeyValue; use opentelemetry::trace::{Span, Status, TraceContextExt, Tracer}; @@ -541,7 +539,7 @@ pub async fn list_memories( match span_mode { // Sort by absolute time for a more 'overview' MemoriesSpan::Month => memories_with_dates.sort_by(|a, b| a.1.cmp(&b.1)), - // For week span, sort by day of month, then time of day, then year (oldest first) + // For week span, sort by day of month, then by full timestamp (oldest first) MemoriesSpan::Week => { memories_with_dates.sort_by(|a, b| { // First, sort by day of month @@ -550,45 +548,12 @@ pub async fn list_memories( return day_cmp; } - // Then sort by time of day + // Then sort by full created timestamp (oldest to newest) match (a.0.created, b.0.created) { - (Some(a_time), Some(b_time)) => { - // Convert timestamps to DateTime - let a_dt_utc = DateTime::::from_timestamp(a_time, 0).unwrap(); - let b_dt_utc = DateTime::::from_timestamp(b_time, 0).unwrap(); - - // Extract time of day in the appropriate timezone - let a_time_of_day = if let Some(ref tz) = client_timezone { - let dt = a_dt_utc.with_timezone(tz); - (dt.hour(), dt.minute(), dt.second()) - } else { - let dt = a_dt_utc.with_timezone(&Local); - (dt.hour(), dt.minute(), dt.second()) - }; - - let b_time_of_day = if let Some(ref tz) = client_timezone { - let dt = b_dt_utc.with_timezone(tz); - (dt.hour(), dt.minute(), dt.second()) - } else { - let dt = b_dt_utc.with_timezone(&Local); - (dt.hour(), dt.minute(), dt.second()) - }; - - // Compare time of day - let time_cmp = a_time_of_day.cmp(&b_time_of_day); - if time_cmp != std::cmp::Ordering::Equal { - return time_cmp; - } - - // Finally, sort by year (oldest first) - a.1.year().cmp(&b.1.year()) - } + (Some(a_time), Some(b_time)) => a_time.cmp(&b_time), (Some(_), None) => std::cmp::Ordering::Less, (None, Some(_)) => std::cmp::Ordering::Greater, - (None, None) => { - // If no timestamps, just sort by year (oldest first) - a.1.year().cmp(&b.1.year()) - } + (None, None) => std::cmp::Ordering::Equal, } }); } @@ -1017,4 +982,90 @@ mod tests { // keep.jpg doesn't match any rule assert!(!excluder.is_excluded(&keep)); } + + #[test] + fn test_week_span_sorting_chronological_by_day() { + // Test that Week span sorts by: + // 1. Day of month (ascending) + // 2. Full timestamp oldest to newest (year + time combined) + + // Create test data: + // - Jan 15, 2024 at 9:00 AM + // - Jan 15, 2020 at 10:00 AM + // - Jan 16, 2021 at 8:00 AM + + let jan_15_2024_9am = NaiveDate::from_ymd_opt(2024, 1, 15) + .unwrap() + .and_hms_opt(9, 0, 0) + .unwrap() + .and_utc() + .timestamp(); + + let jan_15_2020_10am = NaiveDate::from_ymd_opt(2020, 1, 15) + .unwrap() + .and_hms_opt(10, 0, 0) + .unwrap() + .and_utc() + .timestamp(); + + let jan_16_2021_8am = NaiveDate::from_ymd_opt(2021, 1, 16) + .unwrap() + .and_hms_opt(8, 0, 0) + .unwrap() + .and_utc() + .timestamp(); + + let mut memories_with_dates = vec![ + ( + MemoryItem { + path: "photo1.jpg".to_string(), + created: Some(jan_15_2024_9am), + modified: Some(jan_15_2024_9am), + }, + NaiveDate::from_ymd_opt(2024, 1, 15).unwrap(), + ), + ( + MemoryItem { + path: "photo2.jpg".to_string(), + created: Some(jan_15_2020_10am), + modified: Some(jan_15_2020_10am), + }, + NaiveDate::from_ymd_opt(2020, 1, 15).unwrap(), + ), + ( + MemoryItem { + path: "photo3.jpg".to_string(), + created: Some(jan_16_2021_8am), + modified: Some(jan_16_2021_8am), + }, + NaiveDate::from_ymd_opt(2021, 1, 16).unwrap(), + ), + ]; + + // Sort using Week span logic + memories_with_dates.sort_by(|a, b| { + // First, sort by day of month + let day_cmp = a.1.day().cmp(&b.1.day()); + if day_cmp != std::cmp::Ordering::Equal { + return day_cmp; + } + + // Then sort by full created timestamp (oldest to newest) + match (a.0.created, b.0.created) { + (Some(a_time), Some(b_time)) => a_time.cmp(&b_time), + (Some(_), None) => std::cmp::Ordering::Less, + (None, Some(_)) => std::cmp::Ordering::Greater, + (None, None) => std::cmp::Ordering::Equal, + } + }); + + // Expected order: + // 1. Jan 15, 2020 at 10:00 AM (oldest Jan 15 photo) + // 2. Jan 15, 2024 at 9:00 AM (newer Jan 15 photo) + // 3. Jan 16, 2021 at 8:00 AM (all Jan 16 photos after Jan 15) + + assert_eq!(memories_with_dates[0].0.created.unwrap(), jan_15_2020_10am); + assert_eq!(memories_with_dates[1].0.created.unwrap(), jan_15_2024_9am); + assert_eq!(memories_with_dates[2].0.created.unwrap(), jan_16_2021_8am); + } }