rusty_bin/monitor/utils/
time.rs

1//! Time utility functions for the monitor module
2//!
3//! This module provides high-precision timing utilities using the quanta crate
4//! for monotonic time measurements and standard library functions for epoch timestamps.
5
6use crate::monitor::storage::naming::SimpleDate;
7use quanta::Clock;
8use rusty_common::time::days_since_epoch_to_date;
9use std::time::{SystemTime, UNIX_EPOCH};
10
11/// High-precision clock for timing operations
12pub struct TimingUtils {
13    /// High-precision monotonic clock
14    clock: Clock,
15}
16
17impl TimingUtils {
18    /// Create a new timing utility instance
19    #[must_use]
20    pub fn new() -> Self {
21        Self {
22            clock: Clock::new(),
23        }
24    }
25
26    /// Get current timestamp in nanoseconds since Unix epoch
27    /// Uses SystemTime for actual epoch timestamps (not monotonic time)
28    pub fn now_nanos(&self) -> u64 {
29        rusty_common::time::get_epoch_timestamp_ns()
30    }
31
32    /// Get current timestamp in milliseconds since Unix epoch
33    pub fn now_millis(&self) -> u64 {
34        self.now_nanos() / 1_000_000
35    }
36
37    /// Get current timestamp in seconds since Unix epoch
38    pub fn now_seconds(&self) -> u64 {
39        self.now_nanos() / 1_000_000_000
40    }
41
42    /// Get monotonic timestamp in nanoseconds since program start
43    /// Use this for performance measurements, durations, and intervals
44    /// WARNING: This is NOT epoch time - do not use for persistence or external APIs
45    pub fn monotonic_nanos(&self) -> u64 {
46        self.clock.raw() // Returns nanoseconds since program start (monotonic time)
47    }
48
49    /// Get monotonic timestamp in milliseconds since program start
50    /// Use this for performance measurements, durations, and intervals
51    /// WARNING: This is NOT epoch time - do not use for persistence or external APIs
52    pub fn monotonic_millis(&self) -> u64 {
53        self.monotonic_nanos() / 1_000_000
54    }
55
56    /// Convert system time to nanoseconds since Unix epoch
57    ///
58    /// Delegates to rusty_common::time::system_time_to_nanos for consistent
59    /// error handling across the entire system.
60    pub fn system_time_to_nanos(time: SystemTime) -> u64 {
61        rusty_common::time::system_time_to_nanos(time)
62    }
63
64    /// Get current date string in YYYYMMDD format
65    pub fn current_date_string() -> String {
66        let now = SystemTime::now();
67        let duration = now.duration_since(UNIX_EPOCH).unwrap_or_else(|e| {
68            log::error!("CRITICAL: System clock error: {e}. Using fallback date.");
69            // Return a very small duration as emergency fallback (1970-01-01)
70            // This is better than panicking for date formatting, but we log the critical error
71            std::time::Duration::from_secs(0)
72        });
73        Self::duration_to_date_string(duration)
74    }
75
76    /// Get current datetime string in ISO format
77    pub fn current_datetime_string() -> String {
78        let now = SystemTime::now();
79        let duration = now.duration_since(UNIX_EPOCH).unwrap_or_else(|e| {
80            log::error!("CRITICAL: System clock error: {e}. Using fallback datetime.");
81            // Return a very small duration as emergency fallback (1970-01-01)
82            // This is better than panicking for datetime formatting, but we log the critical error
83            std::time::Duration::from_secs(0)
84        });
85        Self::duration_to_datetime_string(duration)
86    }
87
88    /// Check if it's a new day (for file rotation)
89    pub fn is_new_day(last_timestamp_nanos: u64, current_timestamp_nanos: u64) -> bool {
90        let last_date = Self::nanos_to_date_string(last_timestamp_nanos);
91        let current_date = Self::nanos_to_date_string(current_timestamp_nanos);
92        last_date != current_date
93    }
94
95    /// Convert nanoseconds timestamp to date string
96    pub fn nanos_to_date_string(timestamp_nanos: u64) -> String {
97        let duration = std::time::Duration::from_nanos(timestamp_nanos);
98        Self::duration_to_date_string(duration)
99    }
100
101    /// Convert nanoseconds timestamp to datetime string
102    pub fn nanos_to_datetime_string(timestamp_nanos: u64) -> String {
103        let duration = std::time::Duration::from_nanos(timestamp_nanos);
104        Self::duration_to_datetime_string(duration)
105    }
106
107    /// Convert duration since Unix epoch to date string in YYYYMMDD format
108    fn duration_to_date_string(duration: std::time::Duration) -> String {
109        let days = duration.as_secs() / 86400; // seconds per day
110        let (year, month, day) = days_since_epoch_to_date(days);
111        format!("{year:04}{month:02}{day:02}")
112    }
113
114    /// Convert duration since Unix epoch to datetime string in ISO format
115    fn duration_to_datetime_string(duration: std::time::Duration) -> String {
116        let total_secs = duration.as_secs();
117        let days = total_secs / 86400;
118        let remaining_secs = total_secs % 86400;
119        let hours = remaining_secs / 3600;
120        let minutes = (remaining_secs % 3600) / 60;
121        let seconds = remaining_secs % 60;
122        let millis = duration.subsec_millis();
123
124        let (year, month, day) = days_since_epoch_to_date(days);
125        format!("{year:04}-{month:02}-{day:02}T{hours:02}:{minutes:02}:{seconds:02}.{millis:03}Z")
126    }
127
128    /// Check if a year is a leap year
129    const fn is_leap_year(year: u32) -> bool {
130        year.is_multiple_of(4) && (!year.is_multiple_of(100) || year.is_multiple_of(400))
131    }
132
133    /// Get date that is N days ago from now
134    pub fn days_ago(days: u32) -> SimpleDate {
135        let now = SystemTime::now();
136        let duration = now.duration_since(UNIX_EPOCH).unwrap_or_else(|e| {
137            log::error!(
138                "CRITICAL: System clock error: {e}. Using current epoch time for date calculation."
139            );
140            // Return current epoch duration as emergency fallback
141            std::time::Duration::from_secs(0)
142        });
143        let current_days = duration.as_secs() / 86400;
144        let target_days = current_days.saturating_sub(days as u64);
145        let (year, month, day) = days_since_epoch_to_date(target_days);
146        SimpleDate::new(year, month, day).unwrap_or_else(|| {
147            log::error!("CRITICAL: Failed to create valid date from calculated values: year={year}, month={month}, day={day}. Using epoch date.");
148            SimpleDate::new(1970, 1, 1).unwrap()
149        })
150    }
151}
152
153impl Default for TimingUtils {
154    fn default() -> Self {
155        Self::new()
156    }
157}
158
159/// Global timing utility instance
160static TIMING: std::sync::OnceLock<TimingUtils> = std::sync::OnceLock::new();
161
162/// Get the global timing utility instance
163pub fn timing() -> &'static TimingUtils {
164    TIMING.get_or_init(TimingUtils::new)
165}
166
167/// Get current timestamp in nanoseconds since Unix epoch
168pub fn now_nanos() -> u64 {
169    timing().now_nanos()
170}
171
172/// Get current timestamp in milliseconds since Unix epoch
173pub fn now_millis() -> u64 {
174    timing().now_millis()
175}
176
177/// Get current timestamp in seconds since Unix epoch
178pub fn now_seconds() -> u64 {
179    timing().now_seconds()
180}
181
182/// Get monotonic timestamp in nanoseconds since program start
183/// Use this for performance measurements, durations, and intervals
184/// WARNING: This is NOT epoch time - do not use for persistence or external APIs
185pub fn monotonic_nanos() -> u64 {
186    timing().monotonic_nanos()
187}
188
189/// Get monotonic timestamp in milliseconds since program start
190/// Use this for performance measurements, durations, and intervals
191/// WARNING: This is NOT epoch time - do not use for persistence or external APIs
192pub fn monotonic_millis() -> u64 {
193    timing().monotonic_millis()
194}
195
196/// Get current date string in YYYYMMDD format
197pub fn current_date() -> String {
198    TimingUtils::current_date_string()
199}
200
201/// Get date that is N days ago from now
202pub fn days_ago(days: u32) -> SimpleDate {
203    TimingUtils::days_ago(days)
204}
205
206/// Get current datetime string in ISO format
207pub fn current_datetime() -> String {
208    TimingUtils::current_datetime_string()
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn test_timing_utils() {
217        let timing = TimingUtils::new();
218
219        let nanos = timing.now_nanos();
220        let millis = timing.now_millis();
221        let seconds = timing.now_seconds();
222
223        assert!(nanos > 0);
224        assert!(millis > 0);
225        assert!(seconds > 0);
226
227        // Check relationships
228        assert!(nanos / 1_000_000 >= millis - 1); // Allow for small timing differences
229        assert!(millis / 1_000 >= seconds - 1);
230    }
231
232    #[test]
233    fn test_date_formatting() {
234        let date = TimingUtils::current_date_string();
235        assert_eq!(date.len(), 8); // YYYYMMDD format
236
237        let datetime = TimingUtils::current_datetime_string();
238        assert!(datetime.contains('T'));
239        assert!(datetime.ends_with('Z'));
240    }
241
242    #[test]
243    fn test_timestamp_conversion() {
244        let timestamp_nanos = 1_640_995_200_000_000_000; // 2022-01-01 00:00:00 UTC
245        let date = TimingUtils::nanos_to_date_string(timestamp_nanos);
246        assert_eq!(date, "20220101");
247
248        let datetime = TimingUtils::nanos_to_datetime_string(timestamp_nanos);
249        assert!(datetime.starts_with("2022-01-01T00:00:00"));
250    }
251
252    #[test]
253    fn test_new_day_detection() {
254        let day1 = 1_640_995_200_000_000_000; // 2022-01-01 00:00:00 UTC
255        let day2 = 1_641_081_600_000_000_000; // 2022-01-02 00:00:00 UTC
256
257        assert!(!TimingUtils::is_new_day(day1, day1 + 3_600_000_000_000)); // Same day, 1 hour later
258        assert!(TimingUtils::is_new_day(day1, day2)); // Different day
259    }
260}