use std::thread;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub fn now() -> Result<u128, String> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis())
.map_err(|e| e.to_string())
}
pub fn elapsed_millis(start: u128) -> Result<u128, String> {
let current = now()?;
Ok(current.saturating_sub(start))
}
pub fn sleep_millis(millis: u64) -> Result<(), String> {
thread::sleep(Duration::from_millis(millis));
Ok(())
}
pub fn duration_secs(millis: u128) -> Result<f64, String> {
Ok(millis as f64 / 1000.0)
}
pub fn format_duration(millis: u128) -> Result<String, String> {
if millis < 1000 {
return Ok(format!("{millis}ms"));
}
let mut remaining = millis;
let days = remaining / (24 * 60 * 60 * 1000);
remaining %= 24 * 60 * 60 * 1000;
let hours = remaining / (60 * 60 * 1000);
remaining %= 60 * 60 * 1000;
let minutes = remaining / (60 * 1000);
remaining %= 60 * 1000;
let seconds = remaining / 1000;
let mut parts = Vec::new();
if days > 0 {
parts.push(format!("{days}d"));
}
if hours > 0 {
parts.push(format!("{hours}h"));
}
if minutes > 0 {
parts.push(format!("{minutes}m"));
}
if seconds > 0 {
parts.push(format!("{seconds}s"));
}
Ok(parts.join(" "))
}
pub fn parse_duration(duration_str: &str) -> Result<u128, String> {
let mut total_millis: u128 = 0;
for part in duration_str.split_whitespace() {
if part.ends_with("ms") {
let value = part
.trim_end_matches("ms")
.parse::<u128>()
.map_err(|e| e.to_string())?;
total_millis += value;
} else if part.ends_with('s') {
let value = part
.trim_end_matches('s')
.parse::<u128>()
.map_err(|e| e.to_string())?;
total_millis += value * 1000;
} else if part.ends_with('m') {
let value = part
.trim_end_matches('m')
.parse::<u128>()
.map_err(|e| e.to_string())?;
total_millis += value * 60 * 1000;
} else if part.ends_with('h') {
let value = part
.trim_end_matches('h')
.parse::<u128>()
.map_err(|e| e.to_string())?;
total_millis += value * 60 * 60 * 1000;
} else if part.ends_with('d') {
let value = part
.trim_end_matches('d')
.parse::<u128>()
.map_err(|e| e.to_string())?;
total_millis += value * 24 * 60 * 60 * 1000;
} else {
return Err(format!("Invalid duration format: {part}"));
}
}
if total_millis == 0 {
return Err("Invalid duration: must have at least one component".to_string());
}
Ok(total_millis)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_now_positive() {
let timestamp = now().expect("operation should succeed in test");
assert!(timestamp > 0);
assert!(timestamp > 946_684_800_000); }
#[test]
fn test_elapsed_millis_basic() {
let start = now().expect("operation should succeed in test");
std::thread::sleep(std::time::Duration::from_millis(10));
let elapsed = elapsed_millis(start).expect("operation should succeed in test");
assert!(elapsed >= 10);
}
#[test]
fn test_sleep_millis() {
let result = sleep_millis(1);
assert!(result.is_ok());
}
#[test]
fn test_duration_secs_conversion() {
assert_eq!(
duration_secs(1000).expect("operation should succeed in test"),
1.0
);
assert!(
(duration_secs(1500).expect("operation should succeed in test") - 1.5).abs() < 0.01
);
}
#[test]
fn test_format_duration_ms() {
assert_eq!(
format_duration(0).expect("operation should succeed in test"),
"0ms"
);
assert_eq!(
format_duration(500).expect("operation should succeed in test"),
"500ms"
);
}
#[test]
fn test_format_duration_seconds() {
assert_eq!(
format_duration(1000).expect("operation should succeed in test"),
"1s"
);
assert_eq!(
format_duration(5000).expect("operation should succeed in test"),
"5s"
);
}
#[test]
fn test_format_duration_minutes() {
assert_eq!(
format_duration(60_000).expect("operation should succeed in test"),
"1m"
);
assert_eq!(
format_duration(90_000).expect("operation should succeed in test"),
"1m 30s"
);
}
#[test]
fn test_format_duration_hours() {
assert_eq!(
format_duration(3_600_000).expect("operation should succeed in test"),
"1h"
);
assert_eq!(
format_duration(5_400_000).expect("operation should succeed in test"),
"1h 30m"
);
}
#[test]
fn test_format_duration_days() {
assert_eq!(
format_duration(86_400_000).expect("operation should succeed in test"),
"1d"
);
assert_eq!(
format_duration(90_000_000).expect("operation should succeed in test"),
"1d 1h"
);
}
#[test]
fn test_parse_duration_simple() {
assert_eq!(
parse_duration("500ms").expect("operation should succeed in test"),
500
);
assert_eq!(
parse_duration("1s").expect("operation should succeed in test"),
1_000
);
assert_eq!(
parse_duration("1m").expect("operation should succeed in test"),
60_000
);
assert_eq!(
parse_duration("1h").expect("operation should succeed in test"),
3_600_000
);
assert_eq!(
parse_duration("1d").expect("operation should succeed in test"),
86_400_000
);
}
#[test]
fn test_parse_duration_compound() {
assert_eq!(
parse_duration("1h 30m").expect("operation should succeed in test"),
5_400_000
);
assert_eq!(
parse_duration("1d 2h").expect("operation should succeed in test"),
93_600_000
);
}
#[test]
fn test_parse_duration_invalid() {
assert!(parse_duration("invalid").is_err());
assert!(parse_duration("10x").is_err());
assert!(parse_duration("").is_err());
assert!(parse_duration("0s").is_err()); }
#[test]
fn test_format_parse_roundtrip() {
for millis in [1000, 60_000, 90_000, 3_600_000, 86_400_000] {
let formatted = format_duration(millis).expect("operation should succeed in test");
let parsed = parse_duration(&formatted).expect("operation should succeed in test");
assert_eq!(parsed, millis);
}
}
#[test]
fn test_now_increases() {
let t1 = now().expect("operation should succeed in test");
std::thread::sleep(std::time::Duration::from_millis(1));
let t2 = now().expect("operation should succeed in test");
assert!(t2 >= t1);
}
#[test]
fn test_elapsed_millis_zero() {
let start = now().expect("operation should succeed in test");
let elapsed = elapsed_millis(start).expect("operation should succeed in test");
assert!(elapsed < 100);
}
#[test]
fn test_elapsed_millis_future() {
let current = now().expect("operation should succeed in test");
let elapsed =
elapsed_millis(current + 1_000_000).expect("operation should succeed in test");
assert_eq!(elapsed, 0);
}
#[test]
fn test_sleep_millis_zero() {
assert!(sleep_millis(0).is_ok());
}
#[test]
fn test_duration_secs_zero() {
assert_eq!(
duration_secs(0).expect("operation should succeed in test"),
0.0
);
}
#[test]
fn test_duration_secs_large() {
let secs = duration_secs(86_400_000).expect("operation should succeed in test");
assert!((secs - 86_400.0).abs() < 0.01);
}
#[test]
fn test_format_duration_complex() {
let millis = 86_400_000 + 2 * 3_600_000 + 30 * 60_000 + 45 * 1_000;
let formatted = format_duration(millis).expect("operation should succeed in test");
assert!(formatted.contains("1d"));
assert!(formatted.contains("2h"));
assert!(formatted.contains("30m"));
assert!(formatted.contains("45s"));
}
#[test]
fn test_format_duration_only_days() {
let formatted = format_duration(172_800_000).expect("operation should succeed in test");
assert_eq!(formatted, "2d");
}
#[test]
fn test_format_duration_999ms() {
let formatted = format_duration(999).expect("operation should succeed in test");
assert_eq!(formatted, "999ms");
}
#[test]
fn test_parse_duration_with_spaces() {
let parsed = parse_duration("1h 30m").expect("operation should succeed in test");
assert_eq!(parsed, 5_400_000);
}
#[test]
fn test_parse_duration_only_days() {
assert_eq!(
parse_duration("2d").expect("operation should succeed in test"),
172_800_000
);
}
#[test]
fn test_parse_duration_large_values() {
let parsed = parse_duration("365d").expect("operation should succeed in test");
assert_eq!(parsed, 365 * 86_400_000);
}
#[test]
fn test_parse_duration_all_components() {
let parsed = parse_duration("1d 2h 3m 4s 5ms").expect("operation should succeed in test");
let expected = 86_400_000 + 2 * 3_600_000 + 3 * 60_000 + 4 * 1_000 + 5;
assert_eq!(parsed, expected);
}
#[test]
fn test_parse_duration_invalid_number() {
assert!(parse_duration("xyzs").is_err());
assert!(parse_duration("-1s").is_err()); }
#[test]
fn test_parse_duration_missing_unit() {
assert!(parse_duration("100").is_err());
}
#[test]
fn test_elapsed_millis_accuracy() {
let start = now().expect("operation should succeed in test");
std::thread::sleep(std::time::Duration::from_millis(50));
let elapsed = elapsed_millis(start).expect("operation should succeed in test");
assert!(elapsed >= 45); }
}