prism_mcp_rs/utils/
time.rs1use super::{UtilError, UtilResult};
4use std::time::{Duration, SystemTime, UNIX_EPOCH};
5
6pub fn unix_timestamp() -> u64 {
8 SystemTime::now()
9 .duration_since(UNIX_EPOCH)
10 .unwrap_or(Duration::from_secs(0))
11 .as_secs()
12}
13
14pub fn unix_timestamp_millis() -> u64 {
16 SystemTime::now()
17 .duration_since(UNIX_EPOCH)
18 .unwrap_or(Duration::from_secs(0))
19 .as_millis() as u64
20}
21
22pub fn timestamp_to_iso8601(timestamp: u64) -> UtilResult<String> {
24 use chrono::DateTime;
25
26 let datetime = DateTime::from_timestamp(timestamp as i64, 0)
27 .ok_or_else(|| UtilError::InvalidInput("Invalid timestamp".to_string()))?;
28
29 Ok(datetime.to_rfc3339())
30}
31
32pub fn iso8601_to_timestamp(iso_string: &str) -> UtilResult<u64> {
34 use chrono::DateTime;
35
36 let datetime = DateTime::parse_from_rfc3339(iso_string)
37 .map_err(|e| UtilError::InvalidInput(format!("Invalid ISO 8601 string: {}", e)))?;
38
39 Ok(datetime.timestamp() as u64)
40}
41
42pub fn format_duration(duration: Duration) -> String {
44 let total_secs = duration.as_secs();
45
46 if total_secs < 60 {
47 format!("{}.{:03}s", total_secs, duration.subsec_millis())
48 } else if total_secs < 3600 {
49 let minutes = total_secs / 60;
50 let seconds = total_secs % 60;
51 format!("{}m {}s", minutes, seconds)
52 } else if total_secs < 86400 {
53 let hours = total_secs / 3600;
54 let minutes = (total_secs % 3600) / 60;
55 format!("{}h {}m", hours, minutes)
56 } else {
57 let days = total_secs / 86400;
58 let hours = (total_secs % 86400) / 3600;
59 format!("{}d {}h", days, hours)
60 }
61}
62
63pub fn parse_duration(duration_str: &str) -> UtilResult<Duration> {
65 let duration_str = duration_str.trim().to_lowercase();
66
67 if let Ok(secs) = duration_str.parse::<f64>() {
68 return Ok(Duration::from_secs_f64(secs));
69 }
70
71 let (number_part, unit_part) = split_number_unit(&duration_str)?;
72 let number: f64 = number_part
73 .parse()
74 .map_err(|_| UtilError::InvalidInput("Invalid number in duration".to_string()))?;
75
76 let multiplier = match unit_part.as_str() {
77 "ms" | "millis" | "milliseconds" => 0.001,
78 "s" | "sec" | "secs" | "seconds" => 1.0,
79 "m" | "min" | "mins" | "minutes" => 60.0,
80 "h" | "hr" | "hrs" | "hours" => 3600.0,
81 "d" | "day" | "days" => 86400.0,
82 _ => {
83 return Err(UtilError::InvalidInput(format!(
84 "Unknown duration unit: {}",
85 unit_part
86 )))
87 }
88 };
89
90 Ok(Duration::from_secs_f64(number * multiplier))
91}
92
93fn split_number_unit(input: &str) -> UtilResult<(String, String)> {
95 let mut number_part = String::new();
96 let mut unit_part = String::new();
97 let mut found_unit = false;
98
99 for c in input.chars() {
100 if c.is_ascii_digit() || c == '.' {
101 if found_unit {
102 return Err(UtilError::InvalidInput(
103 "Number after unit not allowed".to_string(),
104 ));
105 }
106 number_part.push(c);
107 } else {
108 found_unit = true;
109 unit_part.push(c);
110 }
111 }
112
113 if number_part.is_empty() {
114 return Err(UtilError::InvalidInput(
115 "No number found in duration".to_string(),
116 ));
117 }
118
119 if unit_part.is_empty() {
120 unit_part = "s".to_string(); }
122
123 Ok((number_part, unit_part))
124}
125
126pub struct Timer {
128 start: std::time::Instant,
129}
130
131impl Timer {
132 pub fn new() -> Self {
133 Self {
134 start: std::time::Instant::now(),
135 }
136 }
137
138 pub fn elapsed(&self) -> Duration {
139 self.start.elapsed()
140 }
141
142 pub fn elapsed_secs(&self) -> f64 {
143 self.elapsed().as_secs_f64()
144 }
145
146 pub fn reset(&mut self) {
147 self.start = std::time::Instant::now();
148 }
149}
150
151impl Default for Timer {
152 fn default() -> Self {
153 Self::new()
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn test_unix_timestamp() {
163 let timestamp = unix_timestamp();
164 assert!(timestamp > 1_600_000_000); }
166
167 #[test]
168 fn test_duration_parsing() {
169 assert_eq!(parse_duration("5s").unwrap(), Duration::from_secs(5));
170 assert_eq!(parse_duration("2m").unwrap(), Duration::from_secs(120));
171 assert_eq!(parse_duration("1h").unwrap(), Duration::from_secs(3600));
172 assert_eq!(parse_duration("500ms").unwrap(), Duration::from_millis(500));
173
174 assert!(parse_duration("invalid").is_err());
175 }
176
177 #[test]
178 fn test_duration_formatting() {
179 assert_eq!(format_duration(Duration::from_secs(5)), "5.000s");
180 assert_eq!(format_duration(Duration::from_secs(65)), "1m 5s");
181 assert_eq!(format_duration(Duration::from_secs(3665)), "1h 1m");
182 }
183
184 #[test]
185 fn test_timer() {
186 let timer = Timer::new();
187 std::thread::sleep(Duration::from_millis(10));
188 assert!(timer.elapsed() >= Duration::from_millis(10));
189 }
190
191 #[test]
192 fn test_iso8601_conversion() {
193 let timestamp = 1640995200; let iso_string = timestamp_to_iso8601(timestamp).unwrap();
195 assert!(iso_string.starts_with("2022-01-01"));
196
197 let parsed_timestamp = iso8601_to_timestamp(&iso_string).unwrap();
198 assert_eq!(parsed_timestamp, timestamp);
199 }
200}