1use std::time::{Duration, SystemTime, UNIX_EPOCH};
4
5#[must_use]
7pub fn unix_timestamp() -> u64 {
8 SystemTime::now()
9 .duration_since(UNIX_EPOCH)
10 .unwrap_or_default()
11 .as_secs()
12}
13
14#[must_use]
16#[allow(clippy::cast_possible_truncation)]
17pub fn unix_timestamp_millis() -> u64 {
18 SystemTime::now()
19 .duration_since(UNIX_EPOCH)
20 .unwrap_or_default()
21 .as_millis() as u64
22}
23
24#[must_use]
26pub fn format_duration(duration: Duration) -> String {
27 let secs = duration.as_secs();
28 let millis = duration.subsec_millis();
29
30 if secs >= 86400 {
31 let days = secs / 86400;
32 let hours = (secs % 86400) / 3600;
33 format!("{days}d {hours}h")
34 } else if secs >= 3600 {
35 let hours = secs / 3600;
36 let minutes = (secs % 3600) / 60;
37 format!("{hours}h {minutes}m")
38 } else if secs >= 60 {
39 let minutes = secs / 60;
40 let seconds = secs % 60;
41 format!("{minutes}m {seconds}s")
42 } else if secs > 0 {
43 format!("{secs}.{millis:03}s")
44 } else {
45 format!("{millis}ms")
46 }
47}
48
49pub fn parse_duration(s: &str) -> Result<Duration, String> {
60 let s = s.trim();
61 if s.is_empty() {
62 return Err("Empty duration string".to_string());
63 }
64
65 let chunks: Vec<&str> = s.split_whitespace().collect();
66
67 if chunks.len() == 1 {
69 return parse_single_duration(chunks[0]);
70 }
71
72 let mut total = Duration::ZERO;
73 for chunk in chunks {
74 total += parse_single_duration(chunk)?;
75 }
76 Ok(total)
77}
78
79fn parse_single_duration(s: &str) -> Result<Duration, String> {
81 if let Some(num_str) = s.strip_suffix("ms") {
82 let num: u64 = num_str
83 .parse()
84 .map_err(|_| format!("Invalid milliseconds value: {num_str}"))?;
85 Ok(Duration::from_millis(num))
86 } else if let Some(num_str) = s.strip_suffix('s') {
87 let num: f64 = num_str
88 .parse()
89 .map_err(|_| format!("Invalid seconds value: {num_str}"))?;
90 Ok(Duration::from_secs_f64(num))
91 } else if let Some(num_str) = s.strip_suffix('m') {
92 let num: u64 = num_str
93 .parse()
94 .map_err(|_| format!("Invalid minutes value: {num_str}"))?;
95 Ok(Duration::from_secs(num * 60))
96 } else if let Some(num_str) = s.strip_suffix('h') {
97 let num: u64 = num_str
98 .parse()
99 .map_err(|_| format!("Invalid hours value: {num_str}"))?;
100 Ok(Duration::from_secs(num * 3600))
101 } else if let Some(num_str) = s.strip_suffix('d') {
102 let num: u64 = num_str
103 .parse()
104 .map_err(|_| format!("Invalid days value: {num_str}"))?;
105 Ok(Duration::from_secs(num * 86400))
106 } else {
107 let num: f64 = s
109 .parse()
110 .map_err(|_| format!("Unknown duration chunk: {s}"))?;
111 Ok(Duration::from_secs_f64(num))
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_parse_duration() {
121 assert_eq!(parse_duration("100ms").unwrap(), Duration::from_millis(100));
122 assert_eq!(parse_duration("5s").unwrap(), Duration::from_secs(5));
123 assert_eq!(parse_duration("2m").unwrap(), Duration::from_secs(120));
124 assert_eq!(parse_duration("1h").unwrap(), Duration::from_secs(3600));
125 assert_eq!(parse_duration("1d").unwrap(), Duration::from_secs(86400));
126 }
127
128 #[test]
129 fn test_parse_duration_compound() {
130 assert_eq!(
131 parse_duration("1h 30m").unwrap(),
132 Duration::from_secs(3600 + 1800)
133 );
134 assert_eq!(
135 parse_duration("2d 6h 30m 500ms").unwrap(),
136 Duration::from_secs(2 * 86400 + 6 * 3600 + 30 * 60) + Duration::from_millis(500)
137 );
138 assert_eq!(
139 parse_duration(" 5s 200ms ").unwrap(),
140 Duration::from_secs(5) + Duration::from_millis(200)
141 );
142 }
143
144 #[test]
145 fn test_parse_duration_errors() {
146 assert!(parse_duration("").is_err());
147 assert!(parse_duration("abc").is_err());
148 assert!(parse_duration("1h abc").is_err());
149 }
150
151 #[test]
152 fn test_format_duration() {
153 assert_eq!(format_duration(Duration::from_millis(500)), "500ms");
154 assert_eq!(format_duration(Duration::from_secs(5)), "5.000s");
155 assert_eq!(format_duration(Duration::from_secs(65)), "1m 5s");
156 assert_eq!(format_duration(Duration::from_secs(3665)), "1h 1m");
157 }
158}