Skip to main content

prism_mcp_rs/utils/
time.rs

1//! Time and date utilities
2
3use super::{UtilError, UtilResult};
4use std::time::{Duration, SystemTime, UNIX_EPOCH};
5
6/// Get current Unix timestamp in seconds
7pub fn unix_timestamp() -> u64 {
8    SystemTime::now()
9        .duration_since(UNIX_EPOCH)
10        .unwrap_or(Duration::from_secs(0))
11        .as_secs()
12}
13
14/// Get current Unix timestamp in milliseconds
15pub 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
22/// Convert Unix timestamp to ISO 8601 string
23pub 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
32/// Parse ISO 8601 string to Unix timestamp
33pub 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
42/// Format duration in human-readable form
43pub 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
63/// Parse duration from human-readable string
64pub 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
93/// Split a string like "5m" into ("5", "m")
94fn 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(); // Default to seconds
121    }
122
123    Ok((number_part, unit_part))
124}
125
126/// Simple timer for measuring elapsed time
127pub 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); // After 2020
165    }
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; // 2022-01-01 00:00:00 UTC
194        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}