use rars::ArchiveFamily;
use std::fs;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub(crate) fn current_filetime() -> u64 {
const FILETIME_UNIX_EPOCH_SECONDS: u64 = 11_644_473_600;
const FILETIME_TICKS_PER_SECOND: u64 = 10_000_000;
let duration = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
let seconds = duration
.as_secs()
.saturating_add(FILETIME_UNIX_EPOCH_SECONDS)
.saturating_mul(FILETIME_TICKS_PER_SECOND);
seconds.saturating_add(u64::from(duration.subsec_nanos() / 100))
}
pub(crate) fn format_filetime_utc(filetime: u64) -> String {
filetime_to_system_time(filetime)
.and_then(format_system_time_utc)
.unwrap_or_else(|| format!("{filetime:#018x}"))
}
pub(crate) fn source_unix_mtime(metadata: &fs::Metadata) -> Option<u32> {
metadata
.modified()
.ok()?
.duration_since(UNIX_EPOCH)
.ok()
.and_then(|duration| u32::try_from(duration.as_secs()).ok())
}
pub(crate) fn source_dos_mtime(metadata: &fs::Metadata) -> u32 {
metadata
.modified()
.ok()
.and_then(system_time_to_dos_time)
.unwrap_or(0)
}
pub(crate) fn extracted_system_time(family: ArchiveFamily, file_time: u32) -> Option<SystemTime> {
match family {
ArchiveFamily::Rar13 | ArchiveFamily::Rar15To40 => dos_time_to_system_time(file_time),
ArchiveFamily::Rar50Plus => unix_seconds_to_system_time(file_time),
_ => None,
}
}
fn system_time_to_dos_time(time: SystemTime) -> Option<u32> {
let seconds = time.duration_since(UNIX_EPOCH).ok()?.as_secs();
let days = i64::try_from(seconds / 86_400).ok()?;
let seconds_of_day = seconds % 86_400;
let (year, month, day) = civil_from_days(days);
if !(1980..=2107).contains(&year) {
return None;
}
let hour = seconds_of_day / 3_600;
let minute = (seconds_of_day % 3_600) / 60;
let second = seconds_of_day % 60;
Some(
((u32::try_from(year - 1980).ok()?) << 25)
| (month << 21)
| (day << 16)
| ((hour as u32) << 11)
| ((minute as u32) << 5)
| ((second as u32) / 2),
)
}
fn dos_time_to_system_time(time: u32) -> Option<SystemTime> {
if time == 0 {
return None;
}
let second = (time & 0x1f) * 2;
let minute = (time >> 5) & 0x3f;
let hour = (time >> 11) & 0x1f;
let day = (time >> 16) & 0x1f;
let month = (time >> 21) & 0x0f;
let year = 1980 + i32::try_from((time >> 25) & 0x7f).ok()?;
if month == 0 || month > 12 || day == 0 || day > 31 || hour > 23 || minute > 59 || second > 59 {
return None;
}
let days = days_from_civil(year, month, day);
let seconds = u64::try_from(days)
.ok()?
.checked_mul(86_400)?
.checked_add(u64::from(hour) * 3_600 + u64::from(minute) * 60 + u64::from(second))?;
Some(UNIX_EPOCH + Duration::from_secs(seconds))
}
fn unix_seconds_to_system_time(seconds: u32) -> Option<SystemTime> {
(seconds != 0).then_some(UNIX_EPOCH + Duration::from_secs(u64::from(seconds)))
}
fn filetime_to_system_time(filetime: u64) -> Option<SystemTime> {
const FILETIME_UNIX_EPOCH_SECONDS: u64 = 11_644_473_600;
const FILETIME_TICKS_PER_SECOND: u64 = 10_000_000;
let seconds = filetime / FILETIME_TICKS_PER_SECOND;
let ticks = filetime % FILETIME_TICKS_PER_SECOND;
let unix_seconds = seconds.checked_sub(FILETIME_UNIX_EPOCH_SECONDS)?;
Some(UNIX_EPOCH + Duration::from_secs(unix_seconds) + Duration::from_nanos(ticks * 100))
}
fn format_system_time_utc(time: SystemTime) -> Option<String> {
let duration = time.duration_since(UNIX_EPOCH).ok()?;
let days = i64::try_from(duration.as_secs() / 86_400).ok()?;
let seconds_of_day = duration.as_secs() % 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;
Some(format!(
"{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z"
))
}
fn civil_from_days(days_since_unix_epoch: i64) -> (i32, u32, u32) {
let z = days_since_unix_epoch + 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 y = 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 = y + i64::from(month <= 2);
(year as i32, month as u32, day as u32)
}
fn days_from_civil(year: i32, month: u32, day: u32) -> i64 {
let year = i64::from(year) - i64::from(month <= 2);
let era = if year >= 0 { year } else { year - 399 } / 400;
let yoe = year - era * 400;
let month = i64::from(month);
let day = i64::from(day);
let doy = (153 * (month + if month > 2 { -3 } else { 9 }) + 2) / 5 + day - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
era * 146_097 + doe - 719_468
}