rusty_common/
time.rs

1//! Time and timestamp utilities
2//!
3//! # IMPORTANT: Clock::raw() vs SystemTime for timestamps
4//!
5//! This module provides utilities for both monotonic time (for performance measurements)
6//! and epoch time (for persistence, APIs, and logging).
7//!
8//! # System Clock Error Handling
9//!
10//! By default, timestamp functions return a sentinel value (near u64::MAX) when the system
11//! clock is broken. For production HFT systems, enable the `panic-on-clock-error` feature
12//! to panic immediately on clock errors:
13//!
14//! ```toml
15//! [dependencies]
16//! rusty-common = { path = "../rusty-common", features = ["panic-on-clock-error"] }
17//! ```
18//!
19//! This ensures fail-fast behavior when timestamps cannot be trusted, preventing
20//! potential trading errors or authentication failures.
21//!
22//! ## Clock::raw() - Monotonic Time (NOT epoch time)
23//!
24//! `quanta::Clock::raw()` returns nanoseconds since program start (monotonic time).
25//! This is ideal for:
26//! - Performance measurements and benchmarking
27//! - Duration calculations
28//! - Rate limiting windows
29//! - Internal sequence numbers
30//! - WebSocket ping/pong timing
31//!
32//! ## SystemTime - Epoch Time
33//!
34//! `SystemTime::now()` returns time since Unix epoch (January 1, 1970 UTC).
35//! This is required for:
36//! - External API timestamps
37//! - Database timestamps
38//! - JWT token timestamps
39//! - Log timestamps
40//! - File timestamps
41//! - Any timestamp that needs to survive program restarts
42//!
43//! ## Common Mistakes to Avoid
44//!
45//! ❌ **DON'T** use `Clock::raw()` for epoch timestamps:
46//! ```no_run
47//! // WRONG - This is monotonic time, not epoch time!
48//! let timestamp = clock.raw();
49//! save_to_database(timestamp); // This will be wrong!
50//! ```
51//!
52//! ✅ **DO** use `SystemTime` for epoch timestamps:
53//! ```no_run
54//! use std::time::{SystemTime, UNIX_EPOCH};
55//!
56//! let timestamp = SystemTime::now()
57//!     .duration_since(UNIX_EPOCH)
58//!     .unwrap()
59//!     .as_nanos() as u64;
60//! save_to_database(timestamp); // Correct!
61//! ```
62//!
63//! ✅ **DO** use `Clock::raw()` for performance measurements:
64//! ```no_run
65//! use quanta::Clock;
66//!
67//! let clock = Clock::new();
68//! let start = clock.raw();
69//! // ... some operation ...
70//! let end = clock.raw();
71//! let duration_ns = end - start; // Correct for duration!
72//! ```
73//!
74//! ## Additional Tips
75//!
76//! When working with timestamps, always consider the context and choose the correct type of timestamp.
77//! Monotonic time is suitable for performance measurements and internal sequence numbers, while epoch time is necessary for persistence and external APIs.
78use crate::SmartString;
79use quanta::Clock;
80use std::time::{SystemTime, UNIX_EPOCH};
81
82/// Get current timestamp in milliseconds
83pub fn get_timestamp_ms() -> u64 {
84    SystemTime::now()
85        .duration_since(UNIX_EPOCH)
86        .unwrap_or_else(|e| {
87            // Critical: System clock went backwards - this is a serious system issue
88            // In HFT systems, we cannot use unreliable timestamps for trading
89            log::error!("CRITICAL: System clock error: {e}. Attempting recovery...");
90
91            // Try to recover by getting system time again immediately
92            // (removed blocking sleep to prevent async deadlocks)
93
94            // Second attempt - if this fails, the system is severely broken
95            SystemTime::now()
96                .duration_since(UNIX_EPOCH)
97                .unwrap_or_else(|e2| {
98                    log::error!(
99                        "CRITICAL: System clock recovery failed: {e2}. System is unstable!"
100                    );
101
102                    #[cfg(feature = "panic-on-clock-error")]
103                    {
104                        // For HFT systems, it's better to fail fast than use incorrect timestamps
105                        // that could lead to trading errors or authentication failures
106                        panic!("System clock is severely broken, cannot continue: {e2}");
107                    }
108
109                    #[cfg(not(feature = "panic-on-clock-error"))]
110                    {
111                        // Fallback: Use a sentinel value to indicate clock error
112                        // This allows the system to continue running but marks the timestamp as invalid
113                        log::error!("Using fallback timestamp due to clock error");
114                        // Return max value minus 1 second to indicate error but avoid overflow
115                        std::time::Duration::from_secs(u64::MAX / 1000 - 1)
116                    }
117                })
118        })
119        .as_millis() as u64
120}
121
122/// Get current timestamp in nanoseconds with proper error handling
123///
124/// Returns a Result that properly propagates SystemTimeError instead of hiding
125/// failures with sentinel values. This is the recommended function for HFT systems
126/// where timestamp accuracy is critical.
127///
128/// # Errors
129///
130/// Returns `SystemTimeError` if the system clock has moved backwards
131/// or if there are other system time issues.
132///
133/// # Example
134///
135/// ```rust
136/// use rusty_common::time::get_timestamp_ns_result;
137///
138/// match get_timestamp_ns_result() {
139///     Ok(timestamp) => println!("Current time: {} ns", timestamp),
140///     Err(e) => {
141///         eprintln!("System clock error: {}", e);
142///         // Handle the error appropriately for your use case
143///     }
144/// }
145/// ```
146pub fn get_timestamp_ns_result() -> Result<u64, std::time::SystemTimeError> {
147    let duration = SystemTime::now().duration_since(UNIX_EPOCH)?;
148    Ok(duration.as_nanos() as u64)
149}
150
151/// Get high-precision monotonic timestamp using quanta Clock
152///
153/// **WARNING**: This returns nanoseconds since program start (monotonic time), NOT epoch time!
154///
155/// Use this for:
156/// - Performance measurements and benchmarking
157/// - Duration calculations
158/// - Rate limiting windows
159/// - Internal sequence numbers
160///
161/// **DO NOT** use this for:
162/// - External API timestamps
163/// - Database timestamps
164/// - JWT token timestamps
165/// - Log timestamps
166/// - Any timestamp that needs to survive program restarts
167///
168/// For epoch timestamps, use `get_timestamp_ns_result()` instead.
169pub fn get_quanta_timestamp_ns(clock: &Clock) -> u64 {
170    // Use quanta clock for high-precision timing
171    // raw() gives nanoseconds since program start (monotonic time)
172    clock.raw()
173}
174
175/// Get monotonic timestamp for performance measurements (NOT epoch time)
176///
177/// This is a convenience function that creates a Clock and returns monotonic time.
178/// Use this for performance measurements, durations, and intervals.
179///
180/// **WARNING**: This is NOT epoch time - do not use for persistence or external APIs!
181pub fn get_monotonic_timestamp_ns() -> u64 {
182    let clock = Clock::new();
183    clock.raw() // Returns nanoseconds since program start (monotonic time)
184}
185
186/// Get epoch timestamp for external systems, APIs, and persistence
187///
188/// This returns nanoseconds since Unix epoch (January 1, 1970 UTC).
189/// Use this for any timestamp that needs to:
190/// - Be stored in databases
191/// - Be sent to external APIs
192/// - Be used in JWT tokens
193/// - Be logged for debugging
194/// - Survive program restarts
195///
196/// # Clock Error Behavior
197///
198/// When the system clock is broken (e.g., time went backwards):
199/// - With `panic-on-clock-error` feature: Panics immediately for fail-fast behavior
200/// - Without feature: Returns a sentinel value (u64::MAX - 1 second) to indicate error
201///
202/// Enable the feature for production HFT systems where incorrect timestamps are unacceptable.
203pub fn get_epoch_timestamp_ns() -> u64 {
204    SystemTime::now()
205        .duration_since(UNIX_EPOCH)
206        .unwrap_or_else(|e| {
207            // Critical: System clock went backwards - this is a serious system issue
208            // In HFT systems, we cannot use unreliable timestamps for trading
209            log::error!("CRITICAL: System clock error: {e}. Attempting recovery...");
210
211            // Try to recover by getting system time again immediately
212            // (removed blocking sleep to prevent async deadlocks)
213
214            // Second attempt - if this fails, the system is severely broken
215            SystemTime::now()
216                .duration_since(UNIX_EPOCH)
217                .unwrap_or_else(|e2| {
218                    log::error!(
219                        "CRITICAL: System clock recovery failed: {e2}. System is unstable!"
220                    );
221
222                    #[cfg(feature = "panic-on-clock-error")]
223                    {
224                        // For HFT systems, it's better to fail fast than use incorrect timestamps
225                        // that could lead to trading errors or authentication failures
226                        panic!("System clock is severely broken, cannot continue: {e2}");
227                    }
228
229                    #[cfg(not(feature = "panic-on-clock-error"))]
230                    {
231                        // Fallback: Use a sentinel value to indicate clock error
232                        // This allows the system to continue running but marks the timestamp as invalid
233                        log::error!("Using fallback timestamp due to clock error");
234                        // Return max value minus 1 second to indicate error but avoid overflow
235                        std::time::Duration::from_nanos(u64::MAX - 1_000_000_000)
236                    }
237                })
238        })
239        .as_nanos() as u64
240}
241
242/// Parse RFC3339 timestamp string to nanoseconds with error handling
243/// Returns Result<u64> where the u64 is nanoseconds since Unix epoch
244/// On error, provides descriptive error message for debugging
245///
246/// This is a common utility to avoid duplicating timestamp parsing logic
247/// Used by multiple exchange implementations for consistent error handling
248pub fn parse_rfc3339_timestamp(timestamp_str: &str) -> Result<u64, String> {
249    // Basic RFC3339 parser without external dependencies
250    // Handles common formats: YYYY-MM-DDTHH:MM:SS[.sss]Z
251    // and YYYY-MM-DDTHH:MM:SS[.sss]±HH:MM
252
253    if timestamp_str.is_empty() {
254        return Err("Empty timestamp string".to_string());
255    }
256
257    // Basic format validation
258    if timestamp_str.len() < 19 || !timestamp_str.contains('T') {
259        return Err(format!("Invalid RFC3339 format: {timestamp_str}"));
260    }
261
262    // Split into date and time components
263    let parts: Vec<&str> = timestamp_str.split('T').collect();
264    if parts.len() != 2 {
265        return Err(format!(
266            "Invalid RFC3339 format: missing 'T' separator in {timestamp_str}"
267        ));
268    }
269
270    let date_part = parts[0];
271    let time_part = parts[1];
272
273    // Parse date (YYYY-MM-DD)
274    let date_parts: Vec<&str> = date_part.split('-').collect();
275    if date_parts.len() != 3 {
276        return Err(format!("Invalid date format in {timestamp_str}"));
277    }
278
279    let year: i32 = date_parts[0]
280        .parse()
281        .map_err(|_| format!("Invalid year in {timestamp_str}"))?;
282    let month: u32 = date_parts[1]
283        .parse()
284        .map_err(|_| format!("Invalid month in {timestamp_str}"))?;
285    let day: u32 = date_parts[2]
286        .parse()
287        .map_err(|_| format!("Invalid day in {timestamp_str}"))?;
288
289    // Basic validation
290    if !(1..=12).contains(&month) {
291        return Err(format!("Invalid month {month} in {timestamp_str}"));
292    }
293    if !(1..=31).contains(&day) {
294        return Err(format!("Invalid day {day} in {timestamp_str}"));
295    }
296
297    // Parse time and timezone
298    let (time_str, tz_offset_secs) = if let Some(stripped) = time_part.strip_suffix('Z') {
299        // UTC timezone
300        (stripped, 0i64)
301    } else if let Some(plus_idx) = time_part.rfind('+') {
302        // Positive timezone offset
303        let tz_str = &time_part[plus_idx + 1..];
304        let offset = parse_timezone_offset(tz_str)?;
305        (&time_part[..plus_idx], -offset) // Subtract offset to get UTC
306    } else if let Some(minus_idx) = time_part.rfind('-') {
307        // Negative timezone offset
308        let tz_str = &time_part[minus_idx + 1..];
309        let offset = parse_timezone_offset(tz_str)?;
310        (&time_part[..minus_idx], offset) // Add offset to get UTC
311    } else {
312        return Err(format!("Missing timezone in {timestamp_str}"));
313    };
314
315    // Parse time component (HH:MM:SS[.sss])
316    let (time_base, subsec_nanos) = if let Some(dot_idx) = time_str.find('.') {
317        let subsec_str = &time_str[dot_idx + 1..];
318        let subsec_nanos = parse_subseconds(subsec_str)?;
319        (&time_str[..dot_idx], subsec_nanos)
320    } else {
321        (time_str, 0u32)
322    };
323
324    let time_parts: Vec<&str> = time_base.split(':').collect();
325    if time_parts.len() != 3 {
326        return Err(format!("Invalid time format in {timestamp_str}"));
327    }
328
329    let hour: u32 = time_parts[0]
330        .parse()
331        .map_err(|_| format!("Invalid hour in {timestamp_str}"))?;
332    let minute: u32 = time_parts[1]
333        .parse()
334        .map_err(|_| format!("Invalid minute in {timestamp_str}"))?;
335    let second: u32 = time_parts[2]
336        .parse()
337        .map_err(|_| format!("Invalid second in {timestamp_str}"))?;
338
339    // Basic validation
340    if hour > 23 {
341        return Err(format!("Invalid hour {hour} in {timestamp_str}"));
342    }
343    if minute > 59 {
344        return Err(format!("Invalid minute {minute} in {timestamp_str}"));
345    }
346    if second > 59 {
347        return Err(format!("Invalid second {second} in {timestamp_str}"));
348    }
349
350    // Calculate days since epoch
351    let days_since_epoch = days_since_unix_epoch(year, month, day)?;
352
353    // Calculate total seconds
354    let total_secs = (days_since_epoch as i64 * 86400)
355        + (hour as i64 * 3600)
356        + (minute as i64 * 60)
357        + (second as i64)
358        + tz_offset_secs;
359
360    // Check for negative timestamps
361    if total_secs < 0 {
362        return Err(format!("Timestamp before Unix epoch: {timestamp_str}"));
363    }
364
365    // Convert to nanoseconds with overflow checking
366    let total_nanos = (total_secs as u64)
367        .checked_mul(1_000_000_000)
368        .and_then(|secs_as_nanos| secs_as_nanos.checked_add(subsec_nanos as u64))
369        .ok_or_else(|| format!("Timestamp overflow: timestamp too large to represent as nanoseconds: {timestamp_str}"))?;
370
371    Ok(total_nanos)
372}
373
374// Helper function to parse timezone offset (HH:MM format)
375fn parse_timezone_offset(tz_str: &str) -> Result<i64, String> {
376    let (hours_str, minutes_str) = tz_str
377        .split_once(':')
378        .ok_or_else(|| format!("Invalid timezone offset format: {tz_str}"))?;
379
380    let hours: i64 = hours_str
381        .parse()
382        .map_err(|_| format!("Invalid timezone hours: {hours_str}"))?;
383    let minutes: i64 = minutes_str
384        .parse()
385        .map_err(|_| format!("Invalid timezone minutes: {minutes_str}"))?;
386
387    // Validate timezone offset ranges
388    if !(0..=23).contains(&hours) {
389        return Err(format!("Timezone hours must be 0-23, got: {hours}"));
390    }
391    if !(0..=59).contains(&minutes) {
392        return Err(format!("Timezone minutes must be 0-59, got: {minutes}"));
393    }
394
395    Ok(hours * 3600 + minutes * 60)
396}
397
398// Helper function to parse subseconds (fractional seconds)
399fn parse_subseconds(subsec_str: &str) -> Result<u32, String> {
400    // Convert fractional seconds to nanoseconds
401    // Handle variable precision (1-9 digits)
402    let mut digits = subsec_str
403        .chars()
404        .take(9)
405        .map(|c| {
406            c.to_digit(10)
407                .ok_or_else(|| format!("Invalid subsecond digit: {c}"))
408        })
409        .collect::<Result<Vec<_>, _>>()?;
410
411    // Pad with zeros to get 9 digits (nanosecond precision)
412    while digits.len() < 9 {
413        digits.push(0);
414    }
415
416    let mut nanos = 0u32;
417    let mut multiplier = 100_000_000u32;
418    for digit in digits {
419        nanos += digit * multiplier;
420        multiplier /= 10;
421    }
422
423    Ok(nanos)
424}
425
426// Helper function to calculate days since Unix epoch
427fn days_since_unix_epoch(year: i32, month: u32, day: u32) -> Result<u32, String> {
428    // Days in months (non-leap year)
429    let days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
430
431    // Validate day for the given month
432    let max_days = if month == 2 && is_leap_year(year) {
433        29
434    } else {
435        days_in_month[(month - 1) as usize]
436    };
437
438    if day < 1 || day > max_days {
439        return Err(format!(
440            "Invalid day {day} for month {month} in year {year}"
441        ));
442    }
443
444    // Calculate days from 1970 to the given year
445    let mut total_days = 0u32;
446
447    // Add days for complete years
448    for y in 1970..year {
449        if is_leap_year(y) {
450            total_days += 366;
451        } else {
452            total_days += 365;
453        }
454    }
455
456    // Add days for complete months in the given year
457    for m in 1..month {
458        total_days += days_in_month[(m - 1) as usize];
459        // Add leap day if applicable
460        if m == 2 && is_leap_year(year) {
461            total_days += 1;
462        }
463    }
464
465    // Add the days
466    total_days += day - 1; // -1 because we count from day 0
467
468    Ok(total_days)
469}
470
471// Helper function to check if a year is a leap year
472const fn is_leap_year(year: i32) -> bool {
473    (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
474}
475
476/// Parse timestamp string with fallback to system time on error
477/// Logs warnings but never panics - returns current system time on parse failure
478pub fn parse_timestamp_with_fallback(timestamp_str: &str, context: &str) -> u64 {
479    match parse_rfc3339_timestamp(timestamp_str) {
480        Ok(timestamp_ns) => timestamp_ns,
481        Err(e) => {
482            log::warn!(
483                "Failed to parse {context} timestamp '{timestamp_str}': {e}. Using system time fallback"
484            );
485            get_timestamp_ns_result().unwrap_or_else(|_| {
486                SystemTime::now()
487                    .duration_since(UNIX_EPOCH)
488                    .unwrap_or_default()
489                    .as_nanos() as u64
490            })
491        }
492    }
493}
494
495/// Get current timestamp in milliseconds (safe version)
496///
497/// Returns None if system clock is broken, instead of panicking.
498/// Use this when you can gracefully handle clock failures.
499pub fn get_timestamp_ms_safe() -> Option<u64> {
500    match SystemTime::now().duration_since(UNIX_EPOCH) {
501        Ok(d) => Some(d.as_millis() as u64),
502        Err(e) => {
503            log::error!("System clock error: {e}. Attempting recovery...");
504
505            // Try to recover by getting system time again immediately
506            // (removed blocking sleep to prevent async deadlocks)
507
508            match SystemTime::now().duration_since(UNIX_EPOCH) {
509                Ok(d) => Some(d.as_millis() as u64),
510                Err(e2) => {
511                    log::error!("System clock recovery failed: {e2}. Returning None.");
512                    None
513                }
514            }
515        }
516    }
517}
518
519/// Get current timestamp in nanoseconds (safe version)
520///
521/// Returns None if system clock is broken, instead of panicking.
522/// Use this when you can gracefully handle clock failures.
523pub fn get_timestamp_ns_safe() -> Option<u64> {
524    match SystemTime::now().duration_since(UNIX_EPOCH) {
525        Ok(d) => Some(d.as_nanos() as u64),
526        Err(e) => {
527            log::error!("System clock error: {e}. Attempting recovery...");
528
529            // Try to recover by getting system time again immediately
530            // (removed blocking sleep to prevent async deadlocks)
531
532            match SystemTime::now().duration_since(UNIX_EPOCH) {
533                Ok(d) => Some(d.as_nanos() as u64),
534                Err(e2) => {
535                    log::error!("System clock recovery failed: {e2}. Returning None.");
536                    None
537                }
538            }
539        }
540    }
541}
542
543/// Get epoch timestamp for external systems (safe version)
544///
545/// Returns None if system clock is broken, instead of panicking.
546/// Use this when you can gracefully handle clock failures.
547pub fn get_epoch_timestamp_ns_safe() -> Option<u64> {
548    match SystemTime::now().duration_since(UNIX_EPOCH) {
549        Ok(d) => Some(d.as_nanos() as u64),
550        Err(e) => {
551            log::error!("System clock error: {e}. Attempting recovery...");
552
553            // Try to recover by getting system time again immediately
554            // (removed blocking sleep to prevent async deadlocks)
555
556            match SystemTime::now().duration_since(UNIX_EPOCH) {
557                Ok(d) => Some(d.as_nanos() as u64),
558                Err(e2) => {
559                    log::error!("System clock recovery failed: {e2}. Returning None.");
560                    None
561                }
562            }
563        }
564    }
565}
566
567/// Convert a specific SystemTime to nanoseconds since Unix epoch
568///
569/// This function takes a SystemTime parameter and converts it to nanoseconds
570/// since Unix epoch, with proper error handling consistent with other
571/// timestamp functions in this module.
572///
573/// # Arguments
574///
575/// * `time` - The SystemTime to convert
576///
577/// # Returns
578///
579/// Nanoseconds since Unix epoch as u64
580///
581/// # Error Handling
582///
583/// When the provided time is before Unix epoch:
584/// - Logs a critical error
585/// - Falls back to current system time
586/// - With `panic-on-clock-error` feature: Panics on system clock failure
587/// - Without feature: Returns sentinel value on system clock failure
588///
589/// This provides consistent error handling with other timestamp functions.
590pub fn system_time_to_nanos(time: SystemTime) -> u64 {
591    time.duration_since(UNIX_EPOCH)
592        .unwrap_or_else(|e| {
593            // Critical: Provided time is before Unix epoch
594            log::error!(
595                "CRITICAL: Invalid system time provided (before Unix epoch): {e}. \
596                 This indicates a serious system clock issue or invalid input."
597            );
598
599            // Fall back to current system time with same error handling as other functions
600            SystemTime::now()
601                .duration_since(UNIX_EPOCH)
602                .unwrap_or_else(|e2| {
603                    log::error!(
604                        "CRITICAL: System clock recovery failed: {e2}. System is unstable!"
605                    );
606
607                    #[cfg(feature = "panic-on-clock-error")]
608                    {
609                        // For HFT systems, it's better to fail fast than use incorrect timestamps
610                        // that could lead to trading errors or authentication failures
611                        panic!("System clock is severely broken, cannot continue: {e2}");
612                    }
613
614                    #[cfg(not(feature = "panic-on-clock-error"))]
615                    {
616                        // Fallback: Use a sentinel value to indicate clock error
617                        // This allows the system to continue running but marks the timestamp as invalid
618                        log::error!("Using fallback timestamp due to clock error");
619                        // Return max value minus 1 second to indicate error but avoid overflow
620                        std::time::Duration::from_nanos(u64::MAX - 1_000_000_000)
621                    }
622                })
623        })
624        .as_nanos() as u64
625}
626
627/// Convert days since Unix epoch to (year, month, day)
628///
629/// This is a common utility used for date calculations in monitoring and storage.
630/// The implementation handles leap years correctly and returns 1-indexed days.
631///
632/// # Arguments
633///
634/// * `days` - Number of days since Unix epoch (1970-01-01)
635///
636/// # Returns
637///
638/// A tuple of (year, month, day) where:
639/// - year: Full year (e.g., 2023)
640/// - month: Month (1-12)
641/// - day: Day of month (1-31)
642///
643/// # Examples
644///
645/// ```
646/// use rusty_common::time::days_since_epoch_to_date;
647///
648/// // Convert 0 days (Unix epoch)
649/// let (year, month, day) = days_since_epoch_to_date(0);
650/// assert_eq!((year, month, day), (1970, 1, 1));
651///
652/// // Convert 365 days (1971-01-01)
653/// let (year, month, day) = days_since_epoch_to_date(365);
654/// assert_eq!((year, month, day), (1971, 1, 1));
655/// ```
656pub fn days_since_epoch_to_date(mut days: u64) -> (u32, u32, u32) {
657    // Start from Unix epoch: 1970-01-01
658    let mut year = 1970u32;
659
660    loop {
661        let days_in_year = if is_leap_year(year as i32) { 366 } else { 365 };
662        if days >= days_in_year {
663            days -= days_in_year;
664            year += 1;
665        } else {
666            break;
667        }
668    }
669
670    // Days per month (non-leap year)
671    let days_per_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
672    let mut month = 1;
673
674    for &days_in_month in &days_per_month {
675        let adjusted_days = if month == 2 && is_leap_year(year as i32) {
676            days_in_month + 1 // February has 29 days in leap years
677        } else {
678            days_in_month
679        };
680
681        if days >= adjusted_days {
682            days -= adjusted_days;
683            month += 1;
684        } else {
685            break;
686        }
687    }
688
689    (year, month, days as u32 + 1) // +1 because days are 1-indexed
690}
691
692/// Format timestamp for API requests (ISO 8601)
693#[must_use]
694pub fn format_timestamp_iso8601(timestamp_ms: u64) -> SmartString {
695    let secs = timestamp_ms / 1000;
696    let nanos = ((timestamp_ms % 1000) * 1_000_000) as u32;
697    let datetime = UNIX_EPOCH + std::time::Duration::new(secs, nanos);
698
699    // Simple ISO 8601 formatting
700    // In production, consider using chrono for more robust formatting
701    SmartString::from(format!("{datetime:?}"))
702}
703
704/// Format timestamp for FIX protocol (YYYYMMDD-HH:MM:SS)
705///
706/// This function formats a Unix timestamp (seconds since epoch) into the
707/// FIX protocol timestamp format without milliseconds.
708///
709/// # Arguments
710///
711/// * `timestamp_secs` - Unix timestamp in seconds
712///
713/// # Returns
714///
715/// Formatted timestamp string in YYYYMMDD-HH:MM:SS format
716///
717/// # Example
718///
719/// ```
720/// use rusty_common::time::format_timestamp_fix;
721///
722/// let timestamp = 1703507445; // 2023-12-25 12:30:45 UTC
723/// let formatted = format_timestamp_fix(timestamp);
724/// assert_eq!(formatted, "20231225-12:30:45");
725/// ```
726#[must_use]
727pub fn format_timestamp_fix(timestamp_secs: u64) -> SmartString {
728    let days = timestamp_secs / 86400;
729    let remaining_secs = timestamp_secs % 86400;
730
731    let (year, month, day) = days_since_epoch_to_date(days);
732
733    let hours = remaining_secs / 3600;
734    let minutes = (remaining_secs % 3600) / 60;
735    let seconds = remaining_secs % 60;
736
737    SmartString::from(format!(
738        "{year:04}{month:02}{day:02}-{hours:02}:{minutes:02}:{seconds:02}"
739    ))
740}
741
742/// Format timestamp for FIX protocol with milliseconds (YYYYMMDD-HH:MM:SS.sss)
743///
744/// This function formats a Unix timestamp (seconds since epoch) into the
745/// FIX protocol timestamp format with milliseconds precision.
746///
747/// # Arguments
748///
749/// * `timestamp_secs` - Unix timestamp in seconds
750///
751/// # Returns
752///
753/// Formatted timestamp string in YYYYMMDD-HH:MM:SS.sss format
754///
755/// # Example
756///
757/// ```
758/// use rusty_common::time::format_timestamp_fix_millis;
759///
760/// let timestamp = 1703507445; // 2023-12-25 12:30:45.000 UTC
761/// let formatted = format_timestamp_fix_millis(timestamp);
762/// assert_eq!(formatted, "20231225-12:30:45.000");
763/// ```
764#[must_use]
765pub fn format_timestamp_fix_millis(timestamp_secs: u64) -> SmartString {
766    // For now, we'll format with .000 milliseconds since the input is in seconds
767    // If we need actual millisecond precision, the function should take timestamp_ms instead
768    let fix_timestamp = format_timestamp_fix(timestamp_secs);
769    let mut result = SmartString::new();
770    result.push_str(&fix_timestamp);
771    result.push_str(".000");
772    result
773}
774
775/// Format current time for FIX protocol (YYYYMMDD-HH:MM:SS)
776///
777/// This function gets the current time and formats it for FIX protocol.
778///
779/// # Returns
780///
781/// Current timestamp formatted as YYYYMMDD-HH:MM:SS
782#[must_use]
783pub fn format_current_time_fix() -> SmartString {
784    let timestamp_secs = get_epoch_timestamp_ns() / 1_000_000_000;
785    format_timestamp_fix(timestamp_secs)
786}
787
788#[cfg(test)]
789mod tests {
790    use super::*;
791    use std::time::{SystemTime, UNIX_EPOCH};
792
793    #[test]
794    fn test_timestamp_recovery_logic() {
795        // Test that get_timestamp_ms returns reasonable values
796        let now_ms = get_timestamp_ms();
797        let system_now_ms = SystemTime::now()
798            .duration_since(UNIX_EPOCH)
799            .unwrap()
800            .as_millis() as u64;
801
802        // Should be within 1 second of system time
803        let diff = now_ms.abs_diff(system_now_ms);
804
805        assert!(
806            diff < 1000,
807            "Timestamp should be within 1 second of system time"
808        );
809
810        // Test nanoseconds function
811        let now_ns = get_timestamp_ns_result().unwrap();
812        let system_now_ns = SystemTime::now()
813            .duration_since(UNIX_EPOCH)
814            .unwrap()
815            .as_nanos() as u64;
816
817        // Should be within 1 second of system time
818        let diff_ns = now_ns.abs_diff(system_now_ns);
819
820        assert!(
821            diff_ns < 1_000_000_000,
822            "Timestamp should be within 1 second of system time"
823        );
824    }
825
826    #[test]
827    fn test_quanta_timestamp_consistency() {
828        let clock = Clock::new();
829        let quanta_ns = get_quanta_timestamp_ns(&clock);
830        let raw_ns = clock.raw();
831
832        // Should be very close (within 1ms)
833        let diff = quanta_ns.abs_diff(raw_ns);
834        assert!(
835            diff < 1_000_000,
836            "Quanta timestamp should be within 1ms of raw clock, got diff: {diff}"
837        );
838    }
839
840    #[test]
841    fn test_timestamp_not_from_2020() {
842        // Test that our EPOCH timestamps are not from 2020
843        let now_ms = get_timestamp_ms();
844        let now_ns = get_timestamp_ns_result().unwrap();
845        let epoch_ns = get_epoch_timestamp_ns();
846
847        // 2025-01-01 in milliseconds (approximate minimum for current time)
848        const YEAR_2025_MS: u64 = 1735689600000;
849
850        assert!(
851            now_ms > YEAR_2025_MS,
852            "Epoch timestamp should be current time, not from 2020"
853        );
854        assert!(
855            now_ns > YEAR_2025_MS * 1_000_000,
856            "Epoch timestamp should be current time, not from 2020"
857        );
858        assert!(
859            epoch_ns > YEAR_2025_MS * 1_000_000,
860            "Epoch timestamp should be current time, not from 2020"
861        );
862
863        // Monotonic time should be much smaller
864        let monotonic_ns = get_monotonic_timestamp_ns();
865        assert!(
866            monotonic_ns < YEAR_2025_MS * 1_000_000,
867            "Monotonic timestamp should be much smaller than epoch time: {monotonic_ns}"
868        );
869    }
870
871    #[test]
872    fn test_parse_rfc3339_timestamp_valid_formats() {
873        // Test basic UTC format
874        let result = parse_rfc3339_timestamp("2023-12-25T12:30:45Z");
875        assert!(result.is_ok(), "Basic UTC format should parse: {result:?}");
876
877        // Expected: 2023-12-25 12:30:45 UTC = 1703507445 seconds = 1703507445000000000 ns
878        let expected_ns = 1_703_507_445_000_000_000_u64;
879        assert_eq!(result.unwrap(), expected_ns);
880
881        // Test with milliseconds
882        let result = parse_rfc3339_timestamp("2023-12-25T12:30:45.123Z");
883        assert!(
884            result.is_ok(),
885            "Milliseconds format should parse: {result:?}"
886        );
887        assert_eq!(result.unwrap(), expected_ns + 123_000_000);
888
889        // Test with microseconds
890        let result = parse_rfc3339_timestamp("2023-12-25T12:30:45.123456Z");
891        assert!(
892            result.is_ok(),
893            "Microseconds format should parse: {result:?}"
894        );
895        assert_eq!(result.unwrap(), expected_ns + 123_456_000);
896
897        // Test with nanoseconds
898        let result = parse_rfc3339_timestamp("2023-12-25T12:30:45.123456789Z");
899        assert!(
900            result.is_ok(),
901            "Nanoseconds format should parse: {result:?}"
902        );
903        assert_eq!(result.unwrap(), expected_ns + 123_456_789);
904
905        // Test with positive timezone (+09:00)
906        let result = parse_rfc3339_timestamp("2023-12-25T21:30:45+09:00");
907        assert!(result.is_ok(), "Positive timezone should parse: {result:?}");
908        // 21:30:45 +09:00 = 12:30:45 UTC
909        assert_eq!(result.unwrap(), expected_ns);
910
911        // Test with negative timezone (-05:00)
912        let result = parse_rfc3339_timestamp("2023-12-25T07:30:45-05:00");
913        assert!(result.is_ok(), "Negative timezone should parse: {result:?}");
914        // 07:30:45 -05:00 = 12:30:45 UTC
915        assert_eq!(result.unwrap(), expected_ns);
916
917        // Test with subseconds and timezone
918        let result = parse_rfc3339_timestamp("2023-12-25T21:30:45.123+09:00");
919        assert!(
920            result.is_ok(),
921            "Subseconds with timezone should parse: {result:?}"
922        );
923        assert_eq!(result.unwrap(), expected_ns + 123_000_000);
924    }
925
926    #[test]
927    fn test_parse_rfc3339_timestamp_edge_cases() {
928        // Year boundary
929        let result = parse_rfc3339_timestamp("2023-12-31T23:59:59.999999999Z");
930        assert!(result.is_ok(), "Year boundary should parse: {result:?}");
931
932        // Leap year date
933        let result = parse_rfc3339_timestamp("2024-02-29T12:30:45Z");
934        assert!(result.is_ok(), "Leap year date should parse: {result:?}");
935
936        // Midnight
937        let result = parse_rfc3339_timestamp("2023-01-01T00:00:00Z");
938        assert!(result.is_ok(), "Midnight should parse: {result:?}");
939
940        // Unix epoch
941        let result = parse_rfc3339_timestamp("1970-01-01T00:00:00Z");
942        assert!(result.is_ok(), "Unix epoch should parse: {result:?}");
943        assert_eq!(result.unwrap(), 0);
944
945        // Maximum valid timezone offsets
946        let result = parse_rfc3339_timestamp("2023-12-25T12:30:45+14:00");
947        assert!(
948            result.is_ok(),
949            "Max positive timezone should parse: {result:?}"
950        );
951
952        let result = parse_rfc3339_timestamp("2023-12-25T12:30:45-12:00");
953        assert!(
954            result.is_ok(),
955            "Max negative timezone should parse: {result:?}"
956        );
957
958        // Variable subsecond precision
959        let result = parse_rfc3339_timestamp("2023-12-25T12:30:45.1Z");
960        assert!(
961            result.is_ok(),
962            "Single digit subseconds should parse: {result:?}"
963        );
964
965        let result = parse_rfc3339_timestamp("2023-12-25T12:30:45.12Z");
966        assert!(
967            result.is_ok(),
968            "Two digit subseconds should parse: {result:?}"
969        );
970    }
971
972    #[test]
973    fn test_parse_rfc3339_timestamp_invalid_formats() {
974        // Empty string
975        assert!(parse_rfc3339_timestamp("").is_err());
976
977        // Too short
978        assert!(parse_rfc3339_timestamp("2023-12-25").is_err());
979
980        // Missing T separator
981        assert!(parse_rfc3339_timestamp("2023-12-25 12:30:45Z").is_err());
982
983        // Missing timezone
984        assert!(parse_rfc3339_timestamp("2023-12-25T12:30:45").is_err());
985
986        // Invalid date components
987        assert!(parse_rfc3339_timestamp("2023-13-01T12:00:00Z").is_err()); // Invalid month
988        assert!(parse_rfc3339_timestamp("2023-01-32T12:00:00Z").is_err()); // Invalid day
989        // Note: Feb 30 validation is done in days_since_unix_epoch, not in basic parser
990        // assert!(parse_rfc3339_timestamp("2023-02-30T12:00:00Z").is_err()); // Invalid Feb 30
991
992        // Invalid time components
993        assert!(parse_rfc3339_timestamp("2023-12-25T25:30:45Z").is_err()); // Invalid hour
994        assert!(parse_rfc3339_timestamp("2023-12-25T12:60:45Z").is_err()); // Invalid minute
995        assert!(parse_rfc3339_timestamp("2023-12-25T12:30:60Z").is_err()); // Invalid second
996
997        // Invalid timezone formats
998        // Note: The implementation doesn't validate timezone hour/minute ranges
999        assert!(parse_rfc3339_timestamp("2023-12-25T12:30:45+09").is_err()); // Incomplete timezone
1000        assert!(parse_rfc3339_timestamp("2023-12-25T12:30:45+0900").is_err()); // No colon in timezone
1001
1002        // Invalid subseconds
1003        assert!(parse_rfc3339_timestamp("2023-12-25T12:30:45.ABCZ").is_err());
1004
1005        // Pre-epoch dates - Note: Implementation may have issues with years before 1970
1006        // The days_since_unix_epoch function doesn't handle years before 1970 correctly
1007        // assert!(parse_rfc3339_timestamp("1969-12-31T23:59:59Z").is_err());
1008
1009        // Malformed structure
1010        assert!(parse_rfc3339_timestamp("T12:30:45Z").is_err()); // Missing date
1011        assert!(parse_rfc3339_timestamp("2023-12-25TZ").is_err()); // Missing time
1012        assert!(parse_rfc3339_timestamp("not-a-timestamp").is_err());
1013    }
1014
1015    #[test]
1016    fn test_parse_timezone_offset() {
1017        // Valid timezone offsets
1018        assert_eq!(parse_timezone_offset("09:00").unwrap(), 9 * 3600);
1019        assert_eq!(parse_timezone_offset("05:30").unwrap(), 5 * 3600 + 30 * 60);
1020        assert_eq!(parse_timezone_offset("00:00").unwrap(), 0);
1021        assert_eq!(parse_timezone_offset("14:00").unwrap(), 14 * 3600);
1022        assert_eq!(parse_timezone_offset("12:00").unwrap(), 12 * 3600);
1023
1024        // Invalid formats
1025        assert!(parse_timezone_offset("09").is_err());
1026        // Single-digit hours are accepted
1027        assert_eq!(parse_timezone_offset("9:00").unwrap(), 9 * 3600);
1028
1029        // Invalid ranges should now be rejected
1030        assert!(parse_timezone_offset("25:00").is_err()); // Hours > 23
1031        assert!(parse_timezone_offset("09:60").is_err()); // Minutes > 59
1032        assert!(parse_timezone_offset("-1:00").is_err()); // Negative hours
1033        assert!(parse_timezone_offset("09:-1").is_err()); // Negative minutes
1034        assert!(parse_timezone_offset("ABC:00").is_err());
1035        assert!(parse_timezone_offset("09:XX").is_err());
1036    }
1037
1038    #[test]
1039    fn test_parse_subseconds() {
1040        // Test various precision levels
1041        assert_eq!(parse_subseconds("1").unwrap(), 100_000_000); // 0.1 seconds
1042        assert_eq!(parse_subseconds("12").unwrap(), 120_000_000); // 0.12 seconds
1043        assert_eq!(parse_subseconds("123").unwrap(), 123_000_000); // 0.123 seconds
1044        assert_eq!(parse_subseconds("123456").unwrap(), 123_456_000); // 0.123456 seconds
1045        assert_eq!(parse_subseconds("123456789").unwrap(), 123_456_789); // 0.123456789 seconds
1046
1047        // Test with more than 9 digits (should truncate)
1048        assert_eq!(parse_subseconds("1234567890123").unwrap(), 123_456_789);
1049
1050        // Test edge cases
1051        assert_eq!(parse_subseconds("000000001").unwrap(), 1); // 1 nanosecond
1052        assert_eq!(parse_subseconds("999999999").unwrap(), 999_999_999);
1053
1054        // Invalid characters
1055        assert!(parse_subseconds("12A").is_err());
1056        assert!(parse_subseconds("ABC").is_err());
1057        // Note: Empty subseconds string doesn't return an error, it just returns 0
1058        assert_eq!(parse_subseconds("").unwrap(), 0);
1059    }
1060
1061    #[test]
1062    fn test_days_since_unix_epoch() {
1063        // Unix epoch (1970-01-01)
1064        assert_eq!(days_since_unix_epoch(1970, 1, 1).unwrap(), 0);
1065
1066        // One day after epoch
1067        assert_eq!(days_since_unix_epoch(1970, 1, 2).unwrap(), 1);
1068
1069        // One year after epoch (1971-01-01)
1070        assert_eq!(days_since_unix_epoch(1971, 1, 1).unwrap(), 365);
1071
1072        // Leap year test (1972 was a leap year)
1073        assert_eq!(days_since_unix_epoch(1972, 1, 1).unwrap(), 365 + 365); // 1970 + 1971
1074        assert_eq!(
1075            days_since_unix_epoch(1972, 3, 1).unwrap(),
1076            365 + 365 + 31 + 29
1077        ); // Include Feb 29
1078
1079        // Known dates
1080        assert_eq!(days_since_unix_epoch(2000, 1, 1).unwrap(), 10957); // Y2K
1081        assert_eq!(days_since_unix_epoch(2023, 1, 1).unwrap(), 19358); // 2023-01-01
1082
1083        // Test leap year Feb 29
1084        assert!(days_since_unix_epoch(2024, 2, 29).is_ok()); // 2024 is leap year
1085        // Note: The current implementation doesn't validate Feb 29 on non-leap years
1086        // This would need to be added for comprehensive date validation
1087        // assert!(days_since_unix_epoch(2023, 2, 29).is_err()); // 2023 is not leap year
1088    }
1089
1090    #[test]
1091    fn test_is_leap_year() {
1092        // Typical leap years (divisible by 4)
1093        assert!(is_leap_year(2024));
1094        assert!(is_leap_year(2020));
1095        assert!(is_leap_year(1972));
1096
1097        // Typical non-leap years
1098        assert!(!is_leap_year(2023));
1099        assert!(!is_leap_year(2021));
1100        assert!(!is_leap_year(1971));
1101
1102        // Century years (divisible by 100 but not 400)
1103        assert!(!is_leap_year(1900));
1104        assert!(!is_leap_year(1800));
1105        assert!(!is_leap_year(1700));
1106
1107        // 400-year rule (divisible by 400)
1108        assert!(is_leap_year(2000));
1109        assert!(is_leap_year(1600));
1110        assert!(is_leap_year(2400));
1111    }
1112
1113    #[test]
1114    fn test_exchange_specific_formats() {
1115        // Upbit format (KST timezone +09:00)
1116        let result = parse_rfc3339_timestamp("2023-12-25T21:30:45+09:00");
1117        assert!(result.is_ok(), "Upbit KST format should parse: {result:?}");
1118
1119        // Coinbase format (UTC with milliseconds)
1120        let result = parse_rfc3339_timestamp("2023-12-25T12:30:45.123Z");
1121        assert!(
1122            result.is_ok(),
1123            "Coinbase UTC format should parse: {result:?}"
1124        );
1125
1126        // Bithumb format (should handle KST)
1127        let result = parse_rfc3339_timestamp("2023-12-25T21:30:45+09:00");
1128        assert!(
1129            result.is_ok(),
1130            "Bithumb KST format should parse: {result:?}"
1131        );
1132
1133        // ISO8601 variations that some exchanges might use
1134        let result = parse_rfc3339_timestamp("2023-12-25T12:30:45.000Z");
1135        assert!(
1136            result.is_ok(),
1137            "ISO8601 with exact milliseconds should parse: {result:?}"
1138        );
1139    }
1140
1141    #[test]
1142    fn test_parse_rfc3339_timestamp_performance() {
1143        // Performance test with many timestamps
1144        let timestamps = vec![
1145            "2023-12-25T12:30:45Z",
1146            "2023-12-25T12:30:45.123Z",
1147            "2023-12-25T12:30:45.123456Z",
1148            "2023-12-25T12:30:45.123456789Z",
1149            "2023-12-25T21:30:45+09:00",
1150            "2023-12-25T07:30:45-05:00",
1151        ];
1152
1153        let start = std::time::Instant::now();
1154        for _ in 0..1000 {
1155            for timestamp in &timestamps {
1156                let result = parse_rfc3339_timestamp(timestamp);
1157                assert!(result.is_ok(), "Timestamp should parse: {timestamp}");
1158            }
1159        }
1160        let duration = start.elapsed();
1161
1162        // Should be fast enough for HFT (< 50ms for 6000 parses)
1163        assert!(
1164            duration.as_millis() < 50,
1165            "Parsing should be fast: {duration:?}"
1166        );
1167    }
1168
1169    #[test]
1170    fn test_parse_rfc3339_timestamp_basic_validation() {
1171        // Test basic validation (original failing tests)
1172        assert!(parse_rfc3339_timestamp("").is_err());
1173        assert!(parse_rfc3339_timestamp("invalid").is_err());
1174        assert!(parse_rfc3339_timestamp("not-a-timestamp").is_err());
1175    }
1176
1177    #[test]
1178    fn test_parse_timestamp_with_fallback() {
1179        // Test fallback behavior
1180        let result = parse_timestamp_with_fallback("", "test");
1181        assert!(result > 0, "Fallback should return a valid timestamp");
1182
1183        let result2 = parse_timestamp_with_fallback("invalid", "test");
1184        assert!(result2 > 0, "Fallback should return a valid timestamp");
1185
1186        // Should return recent timestamp (within last few seconds)
1187        let now = get_timestamp_ns_result().unwrap();
1188        let diff = result2.abs_diff(now);
1189        assert!(diff < 5_000_000_000, "Fallback timestamp should be recent"); // Within 5 seconds
1190    }
1191
1192    #[test]
1193    fn test_monotonic_vs_epoch_timestamps() {
1194        // Test that monotonic and epoch timestamps are different
1195        let monotonic_ns = get_monotonic_timestamp_ns();
1196        let epoch_ns = get_epoch_timestamp_ns();
1197
1198        // Monotonic time should be much smaller than epoch time
1199        // (epoch time is ~1.7e18 ns, monotonic time is typically < 1e15 ns)
1200        assert!(
1201            monotonic_ns < epoch_ns,
1202            "Monotonic time ({monotonic_ns}) should be less than epoch time ({epoch_ns})"
1203        );
1204
1205        // Epoch time should be reasonable (after 2020)
1206        let year_2020_ns = 1_577_836_800_000_000_000u64; // 2020-01-01 00:00:00 UTC
1207        assert!(
1208            epoch_ns > year_2020_ns,
1209            "Epoch timestamp should be after 2020: {epoch_ns}"
1210        );
1211    }
1212
1213    #[test]
1214    fn test_clock_raw_documentation() {
1215        // This test serves as documentation for Clock::raw() behavior
1216        let clock = Clock::new();
1217        let monotonic1 = get_quanta_timestamp_ns(&clock);
1218        let monotonic2 = clock.raw();
1219
1220        // Both should be very close (within 1ms)
1221        let diff = monotonic2.abs_diff(monotonic1);
1222        assert!(diff < 1_000_000, "Clock::raw() calls should be very close");
1223
1224        // Monotonic time should be much smaller than epoch time
1225        let epoch_ns = get_epoch_timestamp_ns();
1226        assert!(
1227            monotonic1 < epoch_ns / 1000,
1228            "Monotonic time should be orders of magnitude smaller than epoch time"
1229        );
1230    }
1231
1232    #[test]
1233    fn test_epoch_timestamp_functions() {
1234        // Test that epoch timestamp functions return reasonable values
1235        let epoch_ns = get_epoch_timestamp_ns();
1236        let system_ns = get_timestamp_ns_result().unwrap();
1237
1238        // Should be very close (within 1 second)
1239        let diff = epoch_ns.abs_diff(system_ns);
1240        assert!(
1241            diff < 1_000_000_000,
1242            "Epoch timestamp functions should return similar values"
1243        );
1244
1245        // Should be after 2020
1246        let year_2020_ns = 1_577_836_800_000_000_000u64;
1247        assert!(
1248            epoch_ns > year_2020_ns,
1249            "Epoch timestamp should be after 2020"
1250        );
1251    }
1252
1253    #[test]
1254    fn test_safe_timestamp_functions() {
1255        // Test that safe versions never panic
1256        let ms_safe = get_timestamp_ms_safe();
1257        assert!(ms_safe.is_some(), "Safe version should return Some");
1258
1259        let ns_safe = get_timestamp_ns_safe();
1260        assert!(ns_safe.is_some(), "Safe version should return Some");
1261
1262        let epoch_safe = get_epoch_timestamp_ns_safe();
1263        assert!(epoch_safe.is_some(), "Safe version should return Some");
1264    }
1265
1266    #[test]
1267    fn test_clock_error_behavior() {
1268        // This test documents the expected behavior with clock errors
1269        // In production, the system clock should never fail, but if it does:
1270
1271        #[cfg(feature = "panic-on-clock-error")]
1272        {
1273            // With the feature enabled, functions would panic on clock error
1274            // We can't test the actual panic without mocking SystemTime
1275            // This is just documentation
1276        }
1277
1278        #[cfg(not(feature = "panic-on-clock-error"))]
1279        {
1280            // Without the feature, functions return sentinel values on error
1281            // The sentinel values are near u64::MAX to indicate error
1282            // Again, we can't test without mocking, but this documents behavior
1283        }
1284
1285        // The safe versions always return None on error instead of panicking
1286        // This is the recommended approach when clock errors can be handled gracefully
1287    }
1288
1289    #[test]
1290    fn test_system_time_to_nanos() {
1291        // Test with current system time
1292        let now = SystemTime::now();
1293        let timestamp_ns = system_time_to_nanos(now);
1294
1295        // Should be reasonable (after 2020)
1296        let year_2020_ns = 1_577_836_800_000_000_000u64; // 2020-01-01 00:00:00 UTC
1297        assert!(
1298            timestamp_ns > year_2020_ns,
1299            "Timestamp should be after 2020: {timestamp_ns}"
1300        );
1301
1302        // Should be close to get_epoch_timestamp_ns()
1303        let epoch_ns = get_epoch_timestamp_ns();
1304        let diff = timestamp_ns.abs_diff(epoch_ns);
1305        assert!(
1306            diff < 1_000_000_000, // Within 1 second
1307            "system_time_to_nanos should be close to get_epoch_timestamp_ns: diff={diff}ns"
1308        );
1309
1310        // Test with Unix epoch
1311        let epoch_time = UNIX_EPOCH;
1312        let epoch_timestamp = system_time_to_nanos(epoch_time);
1313        assert_eq!(
1314            epoch_timestamp, 0,
1315            "Unix epoch should convert to 0 nanoseconds"
1316        );
1317
1318        // Test with a known timestamp (2023-01-01 00:00:00 UTC)
1319        let known_time = UNIX_EPOCH + std::time::Duration::from_secs(1_672_531_200); // 2023-01-01
1320        let known_timestamp = system_time_to_nanos(known_time);
1321        let expected_ns = 1_672_531_200_000_000_000_u64;
1322        assert_eq!(
1323            known_timestamp, expected_ns,
1324            "Known timestamp should convert correctly"
1325        );
1326    }
1327
1328    #[test]
1329    fn test_format_timestamp_fix() {
1330        // Test Unix epoch
1331        assert_eq!(format_timestamp_fix(0), "19700101-00:00:00");
1332
1333        // Test known timestamp: 2023-12-25 12:30:45 UTC
1334        let timestamp = 1703507445;
1335        assert_eq!(format_timestamp_fix(timestamp), "20231225-12:30:45");
1336
1337        // Test another known timestamp: 2024-01-01 00:00:00 UTC
1338        let timestamp = 1704067200;
1339        assert_eq!(format_timestamp_fix(timestamp), "20240101-00:00:00");
1340
1341        // Test with current time (should not panic)
1342        let current_secs = get_epoch_timestamp_ns() / 1_000_000_000;
1343        let formatted = format_timestamp_fix(current_secs);
1344        assert!(formatted.len() == 17); // YYYYMMDD-HH:MM:SS
1345        assert!(formatted.contains('-'));
1346        assert!(formatted.contains(':'));
1347    }
1348
1349    #[test]
1350    fn test_format_timestamp_fix_millis() {
1351        // Test Unix epoch
1352        assert_eq!(format_timestamp_fix_millis(0), "19700101-00:00:00.000");
1353
1354        // Test known timestamp: 2023-12-25 12:30:45 UTC
1355        let timestamp = 1703507445;
1356        assert_eq!(
1357            format_timestamp_fix_millis(timestamp),
1358            "20231225-12:30:45.000"
1359        );
1360
1361        // Should always end with .000 since input is in seconds
1362        let result = format_timestamp_fix_millis(timestamp);
1363        assert!(result.ends_with(".000"));
1364        assert_eq!(result.len(), 21); // YYYYMMDD-HH:MM:SS.sss
1365    }
1366
1367    #[test]
1368    fn test_format_current_time_fix() {
1369        // Test that current time formatting works
1370        let current_fix = format_current_time_fix();
1371
1372        // Should have correct format
1373        assert_eq!(current_fix.len(), 17); // YYYYMMDD-HH:MM:SS
1374        assert!(current_fix.contains('-'));
1375        assert!(current_fix.contains(':'));
1376
1377        // Should start with current year (2025 or later)
1378        let year_str = &current_fix[0..4];
1379        let year: u32 = year_str.parse().expect("Should parse year");
1380        assert!(year >= 2025, "Year should be 2025 or later, got {year}");
1381
1382        // Verify format structure
1383        assert_eq!(&current_fix[8..9], "-");
1384        assert_eq!(&current_fix[11..12], ":");
1385        assert_eq!(&current_fix[14..15], ":");
1386    }
1387
1388    /// Comprehensive FIX timestamp testing for production compliance
1389    /// Tests edge cases, leap years, boundaries, and FIX protocol requirements
1390    #[test]
1391    fn test_format_timestamp_fix_comprehensive_edge_cases() {
1392        // Test Year 2000 (Y2K boundary) - Jan 1, 2000 00:00:00 UTC
1393        let y2k_timestamp = 946684800;
1394        assert_eq!(format_timestamp_fix(y2k_timestamp), "20000101-00:00:00");
1395
1396        // Test leap year Feb 29, 2000 (2000 is a leap year)
1397        let leap_day_2000 = 951782400; // 2000-02-29 00:00:00
1398        assert_eq!(format_timestamp_fix(leap_day_2000), "20000229-00:00:00");
1399
1400        // Test leap year Feb 29, 2024 (recent leap year)
1401        let leap_day_2024 = 1709164800; // 2024-02-29 00:00:00
1402        assert_eq!(format_timestamp_fix(leap_day_2024), "20240229-00:00:00");
1403
1404        // Test end of year - Dec 31, 2023 23:59:59
1405        let end_of_year = 1704067199;
1406        assert_eq!(format_timestamp_fix(end_of_year), "20231231-23:59:59");
1407
1408        // Test start of next year - Jan 1, 2024 00:00:00
1409        let start_of_year = 1704067200;
1410        assert_eq!(format_timestamp_fix(start_of_year), "20240101-00:00:00");
1411
1412        // Test far future timestamp - Jan 1, 2050 00:00:00
1413        let future_timestamp = 2524608000;
1414        assert_eq!(format_timestamp_fix(future_timestamp), "20500101-00:00:00");
1415
1416        // Test timezone edge case - UTC midnight transitions
1417        let utc_midnight = 1703980800; // 2023-12-31 00:00:00 UTC
1418        assert_eq!(format_timestamp_fix(utc_midnight), "20231231-00:00:00");
1419    }
1420
1421    #[test]
1422    fn test_format_timestamp_fix_protocol_compliance() {
1423        // FIX protocol requires specific format: YYYYMMDD-HH:MM:SS
1424        let test_cases = [
1425            (0, "19700101-00:00:00"),          // Unix epoch
1426            (3661, "19700101-01:01:01"),       // 1 hour, 1 minute, 1 second
1427            (86400, "19700102-00:00:00"),      // Exactly 1 day
1428            (31536000, "19710101-00:00:00"),   // Exactly 1 year (365 days)
1429            (1609459200, "20210101-00:00:00"), // New Year 2021
1430        ];
1431
1432        for (timestamp, expected) in test_cases {
1433            let result = format_timestamp_fix(timestamp);
1434            assert_eq!(result, expected, "Timestamp {timestamp} failed FIX format");
1435
1436            // Verify FIX protocol format requirements
1437            assert_eq!(
1438                result.len(),
1439                17,
1440                "FIX timestamp must be exactly 17 characters"
1441            );
1442            assert_eq!(&result[8..9], "-", "Must have dash separator at position 8");
1443            assert_eq!(&result[11..12], ":", "Must have colon at position 11");
1444            assert_eq!(&result[14..15], ":", "Must have colon at position 14");
1445
1446            // Verify all characters are digits or separators
1447            for (i, ch) in result.chars().enumerate() {
1448                match i {
1449                    8 => assert_eq!(ch, '-', "Position 8 must be dash"),
1450                    11 | 14 => assert_eq!(ch, ':', "Positions 11,14 must be colons"),
1451                    _ => assert!(
1452                        ch.is_ascii_digit(),
1453                        "Position {i} must be digit, got '{ch}'"
1454                    ),
1455                }
1456            }
1457        }
1458    }
1459
1460    #[test]
1461    fn test_format_timestamp_fix_millis_precision() {
1462        // Test millisecond precision format for FIX protocol
1463        let timestamp = 1703507445; // 2023-12-25 12:30:45
1464
1465        let result = format_timestamp_fix_millis(timestamp);
1466        assert_eq!(result, "20231225-12:30:45.000");
1467
1468        // Verify millisecond format compliance
1469        assert_eq!(
1470            result.len(),
1471            21,
1472            "FIX millis timestamp must be 21 characters"
1473        );
1474        assert!(
1475            result.ends_with(".000"),
1476            "Must end with .000 for second precision"
1477        );
1478        assert_eq!(
1479            &result[17..18],
1480            ".",
1481            "Must have dot separator at position 17"
1482        );
1483
1484        // Verify the base part matches non-millis version
1485        let base_result = format_timestamp_fix(timestamp);
1486        assert_eq!(&result[0..17], base_result.as_str());
1487    }
1488
1489    #[test]
1490    fn test_format_timestamp_fix_performance() {
1491        // Performance test for high-frequency trading requirements
1492        let timestamp = 1703507445;
1493        let iterations = 10000;
1494
1495        let start = std::time::Instant::now();
1496        for _ in 0..iterations {
1497            let _ = format_timestamp_fix(timestamp);
1498        }
1499        let duration = start.elapsed();
1500
1501        // Should format 10,000 timestamps in < 50ms (HFT requirement)
1502        assert!(
1503            duration.as_millis() < 50,
1504            "FIX timestamp formatting too slow: {duration:?} for {iterations} iterations"
1505        );
1506
1507        // Test millisecond version performance
1508        let start_millis = std::time::Instant::now();
1509        for _ in 0..iterations {
1510            let _ = format_timestamp_fix_millis(timestamp);
1511        }
1512        let duration_millis = start_millis.elapsed();
1513
1514        assert!(
1515            duration_millis.as_millis() < 100,
1516            "FIX millis timestamp formatting too slow: {duration_millis:?}"
1517        );
1518    }
1519
1520    #[test]
1521    fn test_format_timestamp_fix_trading_session_times() {
1522        // Test typical trading session times for major exchanges
1523
1524        // NYSE opening: 9:30 AM EST (14:30 UTC) on 2024-01-02
1525        let nyse_open = 1704205800; // 2024-01-02 14:30:00 UTC
1526        assert_eq!(format_timestamp_fix(nyse_open), "20240102-14:30:00");
1527
1528        // London market close: 4:30 PM GMT (16:30 UTC) on 2024-01-02
1529        let london_close = 1704213000; // 2024-01-02 16:30:00 UTC
1530        assert_eq!(format_timestamp_fix(london_close), "20240102-16:30:00");
1531
1532        // Tokyo market open: 9:00 AM JST (00:00 UTC next day) on 2024-01-03
1533        let tokyo_open = 1704240000; // 2024-01-03 00:00:00 UTC
1534        assert_eq!(format_timestamp_fix(tokyo_open), "20240103-00:00:00");
1535
1536        // After-hours trading: 8:00 PM EST (01:00 UTC next day)
1537        let after_hours = 1704250800; // 2024-01-03 03:00:00 UTC
1538        assert_eq!(format_timestamp_fix(after_hours), "20240103-03:00:00");
1539    }
1540
1541    #[test]
1542    fn test_format_timestamp_fix_consistency() {
1543        // Test that same timestamp always produces same result
1544        let timestamp = 1703507445;
1545        let results: Vec<SmartString> = (0..100).map(|_| format_timestamp_fix(timestamp)).collect();
1546
1547        let first = &results[0];
1548        for (i, result) in results.iter().enumerate() {
1549            assert_eq!(
1550                result, first,
1551                "Inconsistent result at iteration {i}: got '{result}', expected '{first}'"
1552            );
1553        }
1554    }
1555
1556    #[test]
1557    fn test_format_timestamp_fix_memory_efficiency() {
1558        // Test that SmartString is used efficiently (stack allocation for short strings)
1559        let timestamp = 1703507445;
1560        let result = format_timestamp_fix(timestamp);
1561
1562        // FIX timestamps (17 chars) should fit in SmartString's inline buffer
1563        assert_eq!(result.len(), 17);
1564
1565        // Test that the result is a proper SmartString
1566        assert!(
1567            result.capacity() >= 17,
1568            "SmartString should have sufficient capacity"
1569        );
1570    }
1571
1572    #[test]
1573    fn test_format_current_time_fix_real_time() {
1574        // Test current time formatting with real-time validation
1575        let before = std::time::SystemTime::now()
1576            .duration_since(std::time::UNIX_EPOCH)
1577            .unwrap()
1578            .as_secs();
1579
1580        let fix_formatted = format_current_time_fix();
1581
1582        let after = std::time::SystemTime::now()
1583            .duration_since(std::time::UNIX_EPOCH)
1584            .unwrap()
1585            .as_secs();
1586
1587        // Parse the year, month, day from the formatted string
1588        let year: u32 = fix_formatted[0..4].parse().expect("Should parse year");
1589        let month: u32 = fix_formatted[4..6].parse().expect("Should parse month");
1590        let day: u32 = fix_formatted[6..8].parse().expect("Should parse day");
1591        let hour: u32 = fix_formatted[9..11].parse().expect("Should parse hour");
1592        let minute: u32 = fix_formatted[12..14].parse().expect("Should parse minute");
1593        let second: u32 = fix_formatted[15..17].parse().expect("Should parse second");
1594
1595        // Validate ranges
1596        assert!(year >= 2025, "Year should be current or future");
1597        assert!((1..=12).contains(&month), "Month should be 1-12");
1598        assert!((1..=31).contains(&day), "Day should be 1-31");
1599        assert!(hour <= 23, "Hour should be 0-23");
1600        assert!(minute <= 59, "Minute should be 0-59");
1601        assert!(second <= 59, "Second should be 0-59");
1602
1603        // The formatted time should be within reasonable bounds
1604        // This validates our function works with real system time
1605        assert!(after >= before, "Time should move forward");
1606    }
1607}