From 11e36bf060ce4a821b1c0d21626533101cd3483f Mon Sep 17 00:00:00 2001 From: Nathaniel Ledford Date: Fri, 9 Oct 2026 10:42:02 -0400 Subject: [PATCH] LR-LIB-CAPTURETIME: a shift past year 9999 (or before 0000) is refused, not skipped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From final review: photo.setCaptureTime quietly skipped photos whose shifted date would leave years 0000–9999. It reported success, with the photo missing from `captured`. Such a shift is now refused for the whole selection with how many photos it concerns, and nothing changes. A date that doesn't read at all is still skipped, as before. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/manage.rs | 19 +++++++++++++++---- crates/engine/src/tests_library.rs | 5 +++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/cmd/manage.rs b/crates/engine/src/cmd/manage.rs index 7232ecdb8c..768fcf9368 100644 --- a/crates/engine/src/cmd/manage.rs +++ b/crates/engine/src/cmd/manage.rs @@ -209,17 +209,28 @@ pub fn specs() -> Vec { } let mut ops = Vec::new(); let mut out = Vec::new(); + let mut out_of_range = 0usize; for id in &targets { - let new = match &each { - Some(t) => shift_iso(t, delta), - None => shift_iso(&base(s, *id), delta), + let from = match &each { + Some(t) => t.clone(), + None => base(s, *id), + }; + let Some(new) = shift_iso(&from, delta) else { + // a date that reads but would leave years 0000–9999 is refused below; one + // that doesn't read is skipped, as before + if iso_seconds(&from).is_some() { + out_of_range += 1; + } + continue; }; - let Some(new) = new else { continue }; out.push(json!(new)); if s.catalog.photo(*id).and_then(|p| p.captured.as_deref()) != Some(new.as_str()) { ops.push(Op::SetCaptured { id: *id, captured: Some(new) }); } } + if out_of_range > 0 { + return Err(bad(c, format!("the shift would take {out_of_range} photo(s) outside the years 0000–9999; nothing was changed"))); + } let n = ops.len(); if n > 0 { s.commit("Edit Capture Time", Op::Batch { ops })?; diff --git a/crates/engine/src/tests_library.rs b/crates/engine/src/tests_library.rs index fd07fa00e0..8a2599e5ca 100644 --- a/crates/engine/src/tests_library.rs +++ b/crates/engine/src/tests_library.rs @@ -492,6 +492,11 @@ fn capture_time_shift_is_bounded() { } s.execute("photo.setCaptureTime", &serde_json::json!({"ids": [id.0], "hours": 1})).unwrap(); assert_ne!(s.catalog.photo(id).unwrap().captured, before); + // a shift that would take a photo past year 9999 says so, rather than skipping it quietly + s.execute("photo.setCaptureTime", &serde_json::json!({"ids": [id.0], "time": "9999-12-31T12:00:00"})).unwrap(); + let r = s.execute("photo.setCaptureTime", &serde_json::json!({"ids": [id.0], "hours": 24})); + assert!(r.as_ref().is_err_and(|e| e.to_string().contains("9999")), "{r:?}"); + assert_eq!(s.catalog.photo(id).unwrap().captured.as_deref(), Some("9999-12-31T12:00:00"), "unchanged"); } /// Values are checked too: an agent's rule set with a date that isn't one, or a rating of 9, is