use anyhow::{Context, Result};
use chrono::{Duration, NaiveDate, TimeZone, Utc};
pub fn resolve_date_range(
weeks: Option<u32>,
from: Option<&str>,
to: Option<&str>,
config_since: Option<&str>,
) -> Result<(Option<String>, Option<String>)> {
if let Some(n) = weeks {
let cutoff = Utc::now() - Duration::weeks(i64::from(n));
return Ok((Some(cutoff.to_rfc3339()), None));
}
if from.is_some() || to.is_some() {
let from_rfc = match from {
Some(s) => Some(parse_iso_date_to_rfc3339(s, false)?),
None => config_since.map(str::to_string),
};
let to_rfc = match to {
Some(s) => Some(parse_iso_date_to_rfc3339(s, true)?),
None => None,
};
return Ok((from_rfc, to_rfc));
}
Ok((config_since.map(str::to_string), None))
}
fn parse_iso_date_to_rfc3339(s: &str, end_of_day: bool) -> Result<String> {
let d: NaiveDate = NaiveDate::parse_from_str(s, "%Y-%m-%d")
.with_context(|| format!("invalid date '{s}' (expected YYYY-MM-DD)"))?;
let (h, m, sec) = if end_of_day { (23, 59, 59) } else { (0, 0, 0) };
let ndt = d
.and_hms_opt(h, m, sec)
.with_context(|| format!("invalid time-of-day for date '{s}'"))?;
Ok(Utc.from_utc_datetime(&ndt).to_rfc3339())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn weeks_wins_over_from_to() {
let (since, until) =
resolve_date_range(Some(2), Some("2024-01-01"), Some("2024-02-01"), None).unwrap();
assert!(since.is_some());
assert!(until.is_none(), "--weeks must leave until open");
}
#[test]
fn from_and_to_parsed() {
let (since, until) =
resolve_date_range(None, Some("2024-01-01"), Some("2024-02-01"), None).unwrap();
assert!(since.unwrap().starts_with("2024-01-01T00:00:00"));
assert!(until.unwrap().starts_with("2024-02-01T23:59:59"));
}
#[test]
fn config_since_fallback() {
let (since, until) = resolve_date_range(None, None, None, Some("2023-06-01")).unwrap();
assert_eq!(since.as_deref(), Some("2023-06-01"));
assert!(until.is_none());
}
#[test]
fn invalid_from_errors() {
let err = resolve_date_range(None, Some("not-a-date"), None, None);
assert!(err.is_err());
}
}