pub fn days_to_ymd(days: u64) -> (u64, u64, u64) {
let z = days as i64 + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = (z - era * 146097) as u64;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let mon = if mp < 10 { mp + 3 } else { mp - 9 };
let y = if mon <= 2 { y + 1 } else { y };
(y as u64, mon, d)
}
#[cfg(test)]
pub fn date_days_ago(days: u64) -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let target = now.saturating_sub(days.saturating_mul(86400));
epoch_to_date(target)
}
#[cfg(test)]
pub fn epoch_to_date(epoch: u64) -> String {
let days_since_epoch = epoch / 86400;
let (year, month, day) = days_to_ymd(days_since_epoch);
format!("{year:04}-{month:02}-{day:02}T00:00:00Z")
}
pub fn parse_relative_days(s: &str) -> Option<u64> {
let s = s.trim().to_lowercase();
let parts: Vec<&str> = s.split_whitespace().collect();
if parts.len() == 2 {
let n: u64 = parts[0].parse().ok()?;
match parts[1].trim_end_matches('s') {
"day" => Some(n),
"week" => n.checked_mul(7),
_ => None,
}
} else {
None
}
}
pub fn parse_iso8601_days(s: &str) -> Option<u64> {
let s = s.trim().trim_matches('"').trim_matches('\'');
let rest = s.strip_prefix(['P', 'p'])?;
let mut chars = rest.chars();
let unit = chars.next_back()?;
let num_part = chars.as_str();
if num_part.is_empty() {
return None;
}
let n: u64 = num_part.parse().ok()?;
match unit {
'D' | 'd' => Some(n),
'W' | 'w' => n.checked_mul(7),
_ => None,
}
}
pub fn parse_duration_minutes(s: &str) -> Option<u64> {
let s = s.trim().trim_matches('"').trim_matches('\'');
let mut chars = s.chars();
let unit = chars.next_back()?;
let num_part = chars.as_str();
if num_part.is_empty() {
return None;
}
let n: u64 = num_part.parse().ok()?;
match unit {
'd' => Some(n.saturating_mul(24 * 60)),
'h' => Some(n.saturating_mul(60)),
'm' => Some(n),
_ => None,
}
}