#[cfg(feature = "encryption")]
pub(crate) fn chrono_date() -> String {
let output = std::process::Command::new("date").arg("+%Y-%m-%d").output();
match output {
Ok(o) => String::from_utf8_lossy(&o.stdout).trim().to_string(),
Err(_) => "unknown".to_string(),
}
}
pub(crate) fn chrono_now_compact() -> String {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
format!("{now}")
}
fn duration_unit_secs(num: u64, unit: char, raw: &str) -> Result<u64, String> {
let per = match unit {
's' => 1,
'm' => 60,
'h' => 3600,
'd' => 86400,
_ => return Err(format!("unknown duration unit '{unit}'. Use s, m, h, or d")),
};
num.checked_mul(per)
.ok_or_else(|| format!("duration overflow: {raw}"))
}
pub(crate) fn parse_duration_secs(s: &str) -> Result<u64, String> {
let s = s.trim();
let unit = s
.chars()
.last()
.ok_or_else(|| format!("invalid duration: '{s}'"))?;
let num_str = &s[..s.len() - unit.len_utf8()];
let n: u64 = num_str
.parse()
.map_err(|_| format!("invalid duration number: '{num_str}'"))?;
duration_unit_secs(n, unit, s)
}
pub(crate) fn parse_duration_string(s: &str) -> Result<u64, String> {
let s = s.trim();
let unit = s
.chars()
.last()
.ok_or_else(|| "empty duration string".to_string())?;
let num_str = &s[..s.len() - unit.len_utf8()];
let num: u64 = num_str
.parse()
.map_err(|_| format!("invalid duration: {s}"))?;
duration_unit_secs(num, unit, s)
}
pub(crate) fn estimate_hours_between(start: &str, end: &str) -> f64 {
let parse_secs = |s: &str| -> Option<i64> {
if s.len() < 19 {
return None;
}
let hours: i64 = s[11..13].parse().ok()?;
let mins: i64 = s[14..16].parse().ok()?;
let secs: i64 = s[17..19].parse().ok()?;
let day: i64 = s[8..10].parse().ok()?;
Some(day * 86400 + hours * 3600 + mins * 60 + secs)
};
match (parse_secs(start), parse_secs(end)) {
(Some(s), Some(e)) => {
let diff = (e - s).max(0) as f64;
diff / 3600.0
}
_ => 1.0, }
}