use std::time::{SystemTime, UNIX_EPOCH};
pub fn current_backup_directory_stamp() -> String {
let seconds = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |duration| duration.as_secs());
backup_directory_stamp_from_unix(seconds)
}
pub fn backup_directory_stamp_from_unix(seconds: u64) -> String {
let days = i64::try_from(seconds / 86_400).unwrap_or(i64::MAX);
let seconds_of_day = seconds % 86_400;
let (year, month, day) = civil_from_days(days);
let hour = seconds_of_day / 3_600;
let minute = (seconds_of_day % 3_600) / 60;
let second = seconds_of_day % 60;
format!("{year:04}{month:02}{day:02}-{hour:02}{minute:02}{second:02}")
}
pub fn backup_directory_stamp_to_unix(stamp: &str) -> Option<u64> {
let (date, time) = stamp.split_once('-')?;
if date.len() != 8
|| time.len() != 6
|| !date.bytes().all(|byte| byte.is_ascii_digit())
|| !time.bytes().all(|byte| byte.is_ascii_digit())
{
return None;
}
let year = date[0..4].parse::<i64>().ok()?;
let month = date[4..6].parse::<i64>().ok()?;
let day = date[6..8].parse::<i64>().ok()?;
let hour = time[0..2].parse::<u64>().ok()?;
let minute = time[2..4].parse::<u64>().ok()?;
let second = time[4..6].parse::<u64>().ok()?;
if !(1..=12).contains(&month) || hour >= 24 || minute >= 60 || second >= 60 {
return None;
}
let days = days_from_civil(year, month, day);
if days < 0 || civil_from_days(days) != (year, month, day) {
return None;
}
u64::try_from(days)
.ok()?
.checked_mul(86_400)?
.checked_add(hour.checked_mul(3_600)?)?
.checked_add(minute.checked_mul(60)?)?
.checked_add(second)
}
pub fn backup_list_timestamp(seconds: u64) -> String {
let days = i64::try_from(seconds / 86_400).unwrap_or(i64::MAX);
let seconds_of_day = seconds % 86_400;
let (year, month, day) = civil_from_days(days);
let hour = seconds_of_day / 3_600;
let minute = (seconds_of_day % 3_600) / 60;
format!("{day:02}/{month:02}/{year:04} {hour:02}:{minute:02}")
}
pub fn unix_timestamp_marker_from_directory_stamp(stamp: &str) -> Option<String> {
backup_directory_stamp_to_unix(stamp).map(|seconds| format!("unix:{seconds}"))
}
pub fn file_safe_component(value: &str) -> String {
let cleaned = value
.chars()
.map(|ch| {
if ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.') {
ch
} else {
'-'
}
})
.collect::<String>();
let cleaned = cleaned.trim_matches('-');
if cleaned.is_empty() {
"unknown".to_string()
} else {
cleaned.to_string()
}
}
const fn days_from_civil(year: i64, month: i64, day: i64) -> i64 {
let year = year - (month <= 2) as i64;
let era = if year >= 0 { year } else { year - 399 } / 400;
let year_of_era = year - era * 400;
let month_prime = month + if month > 2 { -3 } else { 9 };
let day_of_year = (153 * month_prime + 2) / 5 + day - 1;
let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
era * 146_097 + day_of_era - 719_468
}
const fn civil_from_days(days: i64) -> (i64, i64, i64) {
let z = days + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = z - era * 146_097;
let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
let year = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = doy - (153 * mp + 2) / 5 + 1;
let month = mp + if mp < 10 { 3 } else { -9 };
let year = year + (month <= 2) as i64;
(year, month, day)
}