Try adding timezone awareness

This commit is contained in:
Cameron
2025-08-11 17:11:02 -04:00
parent 6aa3c932fb
commit 8d9a5fd79f
2 changed files with 70 additions and 8 deletions

View File

@@ -1,6 +1,6 @@
use actix_web::web::Data;
use actix_web::{get, web, HttpRequest, HttpResponse, Responder};
use chrono::{DateTime, Datelike, Local, NaiveDate, Utc};
use chrono::{DateTime, Datelike, FixedOffset, Local, NaiveDate, TimeZone, Utc};
use opentelemetry::trace::{Span, Status, Tracer};
use opentelemetry::KeyValue;
use serde::{Deserialize, Serialize};
@@ -23,6 +23,8 @@ pub enum MemoriesSpan {
#[derive(Deserialize)]
pub struct MemoriesRequest {
pub span: Option<MemoriesSpan>,
/// Client timezone offset in minutes from UTC (e.g., -480 for PST, 60 for CET)
pub timezone_offset_minutes: Option<i32>,
}
#[derive(Debug, Serialize, Clone)]
@@ -51,7 +53,24 @@ pub async fn list_memories(
let span_mode = q.span.unwrap_or(MemoriesSpan::Day);
let years_back: u32 = 15;
let now = Local::now().date_naive();
// Create timezone from client offset, default to local timezone if not provided
let client_timezone = match q.timezone_offset_minutes {
Some(offset_mins) => {
let offset_secs = offset_mins * 60;
Some(
FixedOffset::east_opt(offset_secs)
.unwrap_or_else(|| FixedOffset::east_opt(0).unwrap()),
)
}
None => None,
};
let now = if let Some(tz) = client_timezone {
Utc::now().with_timezone(&tz).date_naive()
} else {
Local::now().date_naive()
};
let base = Path::new(&app_state.base_path);
let mut memories_with_dates: Vec<(MemoryItem, NaiveDate)> = Vec::new();
@@ -68,7 +87,7 @@ pub async fn list_memories(
}
// Use created date if available, otherwise modified date for matching
let file_date = match file_best_local_date(path) {
let file_date = match file_best_date(path, &client_timezone) {
Some(d) => d,
None => continue,
};
@@ -88,7 +107,8 @@ pub async fn list_memories(
}
}
memories_with_dates.sort_by_key(|k| k.1.ordinal());
// Sort by day of the month
memories_with_dates.sort_by_key(|k| k.1.day());
let items: Vec<MemoryItem> = memories_with_dates.into_iter().map(|(m, _)| m).collect();
@@ -98,6 +118,13 @@ pub async fn list_memories(
KeyValue::new("span", format!("{:?}", span_mode)),
KeyValue::new("years_back", years_back.to_string()),
KeyValue::new("result_count", items.len().to_string()),
KeyValue::new(
"client_timezone",
format!(
"{:?}",
client_timezone.unwrap_or_else(|| FixedOffset::east_opt(0).unwrap())
),
),
],
);
span.set_status(Status::Ok);
@@ -105,11 +132,19 @@ pub async fn list_memories(
HttpResponse::Ok().json(MemoriesResponse { items })
}
fn file_best_local_date(path: &Path) -> Option<NaiveDate> {
fn file_best_date(path: &Path, client_timezone: &Option<FixedOffset>) -> Option<NaiveDate> {
let meta = std::fs::metadata(path).ok()?;
let system_time = meta.created().ok().or_else(|| meta.modified().ok())?;
let local_dt = chrono::DateTime::<Local>::from(system_time);
Some(local_dt.date_naive())
let dt = if let Some(tz) = client_timezone {
let utc_dt: DateTime<Utc> = system_time.into();
utc_dt.with_timezone(tz).date_naive()
} else {
let local_dt = chrono::DateTime::<Local>::from(system_time);
local_dt.date_naive()
};
Some(dt)
}
fn file_times_epoch_secs(path: &Path) -> (Option<i64>, Option<i64>) {
@@ -228,4 +263,30 @@ mod tests {
assert!(!is_memories_match(file_date, today, MemoriesSpan::Day, 10));
assert!(is_memories_match(file_date, today, MemoriesSpan::Day, 20));
}
#[test]
fn test_timezone_conversion() {
// Test file_best_date with different timezones
use std::fs::File;
use tempfile::tempdir;
let temp_dir = tempdir().unwrap();
let temp_file = temp_dir.path().join("test_file.jpg");
File::create(&temp_file).unwrap();
// Test with PST (-8 hours = -480 minutes)
let pst_offset = FixedOffset::west_opt(8 * 3600).unwrap();
let client_tz = Some(pst_offset);
let date = file_best_date(&temp_file, &client_tz);
assert!(date.is_some());
// Test with no timezone (local)
let date_local = file_best_date(&temp_file, &None);
assert!(date_local.is_some());
// Both should return valid dates
assert!(date.unwrap().year() >= 2020);
assert!(date_local.unwrap().year() >= 2020);
}
}

View File

@@ -75,6 +75,7 @@ impl AppState {
#[cfg(test)]
fn create_test_subdir(base_path: &std::path::Path, name: &str) -> std::path::PathBuf {
let dir_path = base_path.join(name);
std::fs::create_dir_all(&dir_path).unwrap_or_else(|_| panic!("Failed to create {} directory", name));
std::fs::create_dir_all(&dir_path)
.unwrap_or_else(|_| panic!("Failed to create {} directory", name));
dir_path
}