ccxt_core/
time.rs

1//! Time utilities for CCXT
2//!
3//! This module provides comprehensive time-related utility functions for timestamp conversion,
4//! date parsing, formatting, and validation. All timestamps are standardized to use `i64` type
5//! representing milliseconds since Unix epoch unless otherwise specified.
6//!
7//! # Key Features
8//!
9//! - **Standardized i64 timestamps**: All timestamps use `i64` for consistency
10//! - **Comprehensive validation**: Range checking and overflow protection
11//! - **Migration support**: Conversion utilities for u64 to i64 migration
12//! - **Multiple format support**: ISO 8601, space-separated, and custom formats
13//! - **UTC timezone handling**: All operations in UTC for consistency
14//! - **Error handling**: Proper error types for all failure modes
15//!
16//! # Timestamp Format Standard
17//!
18//! - **Type**: `i64`
19//! - **Unit**: Milliseconds since Unix Epoch (January 1, 1970, 00:00:00 UTC)
20//! - **Range**: 0 to ~292,277,026,596 (year ~294,276)
21//! - **Validation**: Must be >= 0 for valid Unix timestamps
22//!
23//! # Example
24//!
25//! ```rust
26//! use ccxt_core::time::{TimestampUtils, milliseconds, iso8601, parse_date};
27//!
28//! // Get current timestamp in milliseconds
29//! let now = milliseconds();
30//!
31//! // Validate timestamp
32//! let validated = TimestampUtils::validate_timestamp(now).unwrap();
33//!
34//! // Convert timestamp to ISO 8601 string
35//! let iso_str = iso8601(validated).unwrap();
36//!
37//! // Parse ISO 8601 string back to timestamp
38//! let parsed = parse_date(&iso_str).unwrap();
39//! assert_eq!(validated, parsed);
40//!
41//! // Migration support: convert u64 to i64
42//! let old_timestamp: u64 = 1704110400000;
43//! let new_timestamp = TimestampUtils::u64_to_i64(old_timestamp).unwrap();
44//! ```
45
46use crate::error::{Error, ParseError, Result};
47use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
48use std::time::{SystemTime, UNIX_EPOCH};
49
50/// Returns the current time in milliseconds since the Unix epoch
51///
52/// This function is now a wrapper around `TimestampUtils::now_ms()` for consistency.
53/// Consider using `TimestampUtils::now_ms()` directly for new code.
54///
55/// # Example
56///
57/// ```rust
58/// use ccxt_core::time::milliseconds;
59///
60/// let now = milliseconds();
61/// assert!(now > 0);
62/// ```
63#[inline]
64pub fn milliseconds() -> i64 {
65    TimestampUtils::now_ms()
66}
67
68/// Returns the current time in seconds since the Unix epoch
69///
70/// This function is now a wrapper around `TimestampUtils` for consistency.
71/// Consider using `TimestampUtils::ms_to_seconds(TimestampUtils::now_ms())` for new code.
72///
73/// # Example
74///
75/// ```rust
76/// use ccxt_core::time::seconds;
77///
78/// let now = seconds();
79/// assert!(now > 0);
80/// ```
81#[inline]
82pub fn seconds() -> i64 {
83    TimestampUtils::ms_to_seconds(TimestampUtils::now_ms())
84}
85
86/// Returns the current time in microseconds since the Unix epoch
87///
88/// # Example
89///
90/// ```rust
91/// use ccxt_core::time::microseconds;
92///
93/// let now = microseconds();
94/// assert!(now > 0);
95/// ```
96#[inline]
97pub fn microseconds() -> i64 {
98    Utc::now().timestamp_micros()
99}
100
101/// Converts a timestamp in milliseconds to an ISO 8601 formatted string
102///
103/// This function now uses `TimestampUtils::format_iso8601()` internally for consistency.
104/// Consider using `TimestampUtils::format_iso8601()` directly for new code.
105///
106/// # Arguments
107///
108/// * `timestamp` - Timestamp in milliseconds since Unix epoch
109///
110/// # Returns
111///
112/// ISO 8601 formatted string in UTC timezone (e.g., "2024-01-01T12:00:00.000Z")
113///
114/// # Example
115///
116/// ```rust
117/// use ccxt_core::time::iso8601;
118///
119/// let timestamp = 1704110400000; // 2024-01-01 12:00:00 UTC
120/// let iso_str = iso8601(timestamp).unwrap();
121/// assert_eq!(iso_str, "2024-01-01T12:00:00.000Z");
122/// ```
123pub fn iso8601(timestamp: i64) -> Result<String> {
124    TimestampUtils::format_iso8601(timestamp)
125}
126
127/// Parses a date string and returns the timestamp in milliseconds since Unix epoch
128///
129/// Supports multiple date formats:
130/// - ISO 8601: "2024-01-01T12:00:00.000Z" or "2024-01-01T12:00:00Z"
131/// - Space-separated: "2024-01-01 12:00:00"
132/// - Without timezone: "2024-01-01T12:00:00.389"
133///
134/// # Arguments
135///
136/// * `datetime` - Date string in one of the supported formats
137///
138/// # Returns
139///
140/// Timestamp in milliseconds since Unix epoch
141///
142/// # Example
143///
144/// ```rust
145/// use ccxt_core::time::parse_date;
146///
147/// let ts1 = parse_date("2024-01-01T12:00:00.000Z").unwrap();
148/// let ts2 = parse_date("2024-01-01 12:00:00").unwrap();
149/// assert!(ts1 > 0);
150/// assert!(ts2 > 0);
151/// ```
152pub fn parse_date(datetime: &str) -> Result<i64> {
153    if datetime.is_empty() {
154        return Err(ParseError::timestamp("Empty datetime string").into());
155    }
156
157    // List of supported date formats
158    let formats = [
159        "%Y-%m-%d %H:%M:%S",      // "2024-01-01 12:00:00"
160        "%Y-%m-%dT%H:%M:%S%.3fZ", // "2024-01-01T12:00:00.000Z"
161        "%Y-%m-%dT%H:%M:%SZ",     // "2024-01-01T12:00:00Z"
162        "%Y-%m-%dT%H:%M:%S%.3f",  // "2024-01-01T12:00:00.389"
163        "%Y-%m-%dT%H:%M:%S",      // "2024-01-01T12:00:00"
164        "%Y-%m-%d %H:%M:%S%.3f",  // "2024-01-01 12:00:00.389"
165    ];
166
167    // Try parsing with each format
168    for format in &formats {
169        if let Ok(naive) = NaiveDateTime::parse_from_str(datetime, format) {
170            let dt = Utc.from_utc_datetime(&naive);
171            return Ok(dt.timestamp_millis());
172        }
173    }
174
175    // Try parsing as RFC3339 (handles timezone offsets)
176    if let Ok(dt) = DateTime::parse_from_rfc3339(datetime) {
177        return Ok(dt.timestamp_millis());
178    }
179
180    Err(ParseError::timestamp_owned(format!("Unable to parse datetime: {datetime}")).into())
181}
182
183/// Parses an ISO 8601 date string and returns the timestamp in milliseconds
184///
185/// This function handles various ISO 8601 formats and strips timezone offsets
186/// like "+00:00" before parsing.
187///
188/// # Arguments
189///
190/// * `datetime` - ISO 8601 formatted date string
191///
192/// # Returns
193///
194/// Timestamp in milliseconds since Unix epoch
195///
196/// # Example
197///
198/// ```rust
199/// use ccxt_core::time::parse_iso8601;
200///
201/// let ts = parse_iso8601("2024-01-01T12:00:00.000Z").unwrap();
202/// assert!(ts > 0);
203///
204/// let ts2 = parse_iso8601("2024-01-01T12:00:00+00:00").unwrap();
205/// assert!(ts2 > 0);
206/// ```
207pub fn parse_iso8601(datetime: &str) -> Result<i64> {
208    if datetime.is_empty() {
209        return Err(ParseError::timestamp("Empty datetime string").into());
210    }
211
212    // Remove "+00:00" or similar timezone offsets if present
213    let cleaned = if datetime.contains("+0") {
214        datetime.split('+').next().unwrap_or(datetime)
215    } else {
216        datetime
217    };
218
219    // Try RFC3339 format first
220    if let Ok(dt) = DateTime::parse_from_rfc3339(cleaned) {
221        return Ok(dt.timestamp_millis());
222    }
223
224    // Try parsing without timezone
225    let formats = [
226        "%Y-%m-%dT%H:%M:%S%.3f", // "2024-01-01T12:00:00.389"
227        "%Y-%m-%d %H:%M:%S%.3f", // "2024-01-01 12:00:43.928"
228        "%Y-%m-%dT%H:%M:%S",     // "2024-01-01T12:00:00"
229        "%Y-%m-%d %H:%M:%S",     // "2024-01-01 12:00:00"
230    ];
231
232    for format in &formats {
233        if let Ok(naive) = NaiveDateTime::parse_from_str(cleaned, format) {
234            let dt = Utc.from_utc_datetime(&naive);
235            return Ok(dt.timestamp_millis());
236        }
237    }
238
239    Err(
240        ParseError::timestamp_owned(format!("Unable to parse ISO 8601 datetime: {datetime}"))
241            .into(),
242    )
243}
244
245/// Formats a timestamp as "yyyy-MM-dd HH:mm:ss"
246///
247/// This function now includes validation using `TimestampUtils::validate_timestamp()`.
248///
249/// # Arguments
250///
251/// * `timestamp` - Timestamp in milliseconds since Unix epoch
252/// * `separator` - Optional separator between date and time (defaults to " ")
253///
254/// # Returns
255///
256/// Formatted date string
257///
258/// # Example
259///
260/// ```rust
261/// use ccxt_core::time::ymdhms;
262///
263/// let ts = 1704110400000; // 2024-01-01 12:00:00 UTC
264/// let formatted = ymdhms(ts, None).unwrap();
265/// assert_eq!(formatted, "2024-01-01 12:00:00");
266///
267/// let formatted_t = ymdhms(ts, Some("T")).unwrap();
268/// assert_eq!(formatted_t, "2024-01-01T12:00:00");
269/// ```
270pub fn ymdhms(timestamp: i64, separator: Option<&str>) -> Result<String> {
271    let validated = TimestampUtils::validate_timestamp(timestamp)?;
272
273    let sep = separator.unwrap_or(" ");
274    let secs = validated / 1000;
275    #[allow(clippy::cast_possible_truncation)]
276    let nsecs = ((validated % 1000) * 1_000_000) as u32;
277
278    let datetime = DateTime::<Utc>::from_timestamp(secs, nsecs)
279        .ok_or_else(|| Error::invalid_request(format!("Invalid timestamp: {validated}")))?;
280
281    Ok(format!(
282        "{}{}{}",
283        datetime.format("%Y-%m-%d"),
284        sep,
285        datetime.format("%H:%M:%S")
286    ))
287}
288
289/// Formats a timestamp as "yyyy-MM-dd"
290///
291/// This function now includes validation using `TimestampUtils::validate_timestamp()`.
292///
293/// # Arguments
294///
295/// * `timestamp` - Timestamp in milliseconds since Unix epoch
296/// * `separator` - Optional separator between year, month, day (defaults to "-")
297///
298/// # Example
299///
300/// ```rust
301/// use ccxt_core::time::yyyymmdd;
302///
303/// let ts = 1704110400000;
304/// let formatted = yyyymmdd(ts, None).unwrap();
305/// assert_eq!(formatted, "2024-01-01");
306///
307/// let formatted_slash = yyyymmdd(ts, Some("/")).unwrap();
308/// assert_eq!(formatted_slash, "2024/01/01");
309/// ```
310pub fn yyyymmdd(timestamp: i64, separator: Option<&str>) -> Result<String> {
311    let validated = TimestampUtils::validate_timestamp(timestamp)?;
312
313    let sep = separator.unwrap_or("-");
314    let secs = validated / 1000;
315    #[allow(clippy::cast_possible_truncation)]
316    let nsecs = ((validated % 1000) * 1_000_000) as u32;
317
318    let datetime = DateTime::<Utc>::from_timestamp(secs, nsecs)
319        .ok_or_else(|| Error::invalid_request(format!("Invalid timestamp: {validated}")))?;
320
321    Ok(format!(
322        "{}{}{}{}{}",
323        datetime.format("%Y"),
324        sep,
325        datetime.format("%m"),
326        sep,
327        datetime.format("%d")
328    ))
329}
330
331/// Formats a timestamp as "yy-MM-dd"
332///
333/// This function now includes validation using `TimestampUtils::validate_timestamp()`.
334///
335/// # Arguments
336///
337/// * `timestamp` - Timestamp in milliseconds since Unix epoch
338/// * `separator` - Optional separator between year, month, day (defaults to "")
339///
340/// # Example
341///
342/// ```rust
343/// use ccxt_core::time::yymmdd;
344///
345/// let ts = 1704110400000;
346/// let formatted = yymmdd(ts, None).unwrap();
347/// assert_eq!(formatted, "240101");
348///
349/// let formatted_dash = yymmdd(ts, Some("-")).unwrap();
350/// assert_eq!(formatted_dash, "24-01-01");
351/// ```
352pub fn yymmdd(timestamp: i64, separator: Option<&str>) -> Result<String> {
353    let validated = TimestampUtils::validate_timestamp(timestamp)?;
354
355    let sep = separator.unwrap_or("");
356    let secs = validated / 1000;
357    #[allow(clippy::cast_possible_truncation)]
358    let nsecs = ((validated % 1000) * 1_000_000) as u32;
359
360    let datetime = DateTime::<Utc>::from_timestamp(secs, nsecs)
361        .ok_or_else(|| Error::invalid_request(format!("Invalid timestamp: {validated}")))?;
362
363    Ok(format!(
364        "{}{}{}{}{}",
365        datetime.format("%y"),
366        sep,
367        datetime.format("%m"),
368        sep,
369        datetime.format("%d")
370    ))
371}
372
373/// Alias for `yyyymmdd` function
374///
375/// # Example
376///
377/// ```rust
378/// use ccxt_core::time::ymd;
379///
380/// let ts = 1704110400000;
381/// let formatted = ymd(ts, None).unwrap();
382/// assert_eq!(formatted, "2024-01-01");
383/// ```
384#[inline]
385pub fn ymd(timestamp: i64, separator: Option<&str>) -> Result<String> {
386    yyyymmdd(timestamp, separator)
387}
388
389/// Comprehensive timestamp utilities for consistent i64 handling
390///
391/// This struct provides a collection of utility functions for working with i64 timestamps,
392/// including validation, conversion, and formatting operations. All functions are designed
393/// to work with milliseconds since Unix epoch.
394///
395/// # Design Principles
396///
397/// - **Type Safety**: All operations use i64 for consistency
398/// - **Validation**: Range checking prevents invalid timestamps
399/// - **Migration Support**: Conversion utilities for u64 to i64 migration
400/// - **Error Handling**: Proper error types for all failure modes
401///
402/// # Example
403///
404/// ```rust
405/// use ccxt_core::time::TimestampUtils;
406///
407/// // Get current timestamp
408/// let now = TimestampUtils::now_ms();
409///
410/// // Validate timestamp
411/// let validated = TimestampUtils::validate_timestamp(now).unwrap();
412///
413/// // Convert units
414/// let seconds = TimestampUtils::ms_to_seconds(validated);
415/// let back_to_ms = TimestampUtils::seconds_to_ms(seconds);
416/// // Note: conversion to seconds loses millisecond precision
417/// assert!(validated - back_to_ms < 1000);
418/// ```
419pub struct TimestampUtils;
420
421impl TimestampUtils {
422    /// Maximum reasonable timestamp (year 2100) to prevent far-future timestamps
423    pub const YEAR_2100_MS: i64 = 4_102_444_800_000;
424
425    /// Minimum reasonable timestamp (year 1970) - Unix epoch start
426    pub const UNIX_EPOCH_MS: i64 = 0;
427
428    /// Get current timestamp in milliseconds as i64
429    ///
430    /// Returns the current system time as milliseconds since Unix epoch.
431    /// This is the primary function for getting current timestamps in the library.
432    ///
433    /// # Example
434    ///
435    /// ```rust
436    /// use ccxt_core::time::TimestampUtils;
437    ///
438    /// let now = TimestampUtils::now_ms();
439    /// assert!(now > 1_600_000_000_000); // After 2020
440    /// ```
441    pub fn now_ms() -> i64 {
442        // SAFETY: SystemTime::now().duration_since(UNIX_EPOCH) can only fail if the system
443        // clock is set to a time before January 1, 1970 (Unix epoch). This is an extremely
444        // rare edge case that would indicate a severely misconfigured system. In practice,
445        // no modern operating system allows the clock to be set before 1970, and any system
446        // running cryptocurrency trading software would have a properly configured clock.
447        // The panic here is intentional to fail fast on such misconfigured systems.
448        #[allow(clippy::cast_possible_truncation)]
449        let ms = SystemTime::now()
450            .duration_since(UNIX_EPOCH)
451            .expect("System clock is set before UNIX_EPOCH (1970); this indicates a severely misconfigured system")
452            .as_millis() as i64;
453        ms
454    }
455
456    /// Convert seconds to milliseconds
457    ///
458    /// # Arguments
459    ///
460    /// * `seconds` - Timestamp in seconds since Unix epoch
461    ///
462    /// # Returns
463    ///
464    /// Timestamp in milliseconds since Unix epoch
465    ///
466    /// # Example
467    ///
468    /// ```rust
469    /// use ccxt_core::time::TimestampUtils;
470    ///
471    /// let seconds = 1704110400; // 2024-01-01 12:00:00 UTC
472    /// let milliseconds = TimestampUtils::seconds_to_ms(seconds);
473    /// assert_eq!(milliseconds, 1704110400000);
474    /// ```
475    pub fn seconds_to_ms(seconds: i64) -> i64 {
476        seconds.saturating_mul(1000)
477    }
478
479    /// Convert milliseconds to seconds
480    ///
481    /// # Arguments
482    ///
483    /// * `ms` - Timestamp in milliseconds since Unix epoch
484    ///
485    /// # Returns
486    ///
487    /// Timestamp in seconds since Unix epoch
488    ///
489    /// # Example
490    ///
491    /// ```rust
492    /// use ccxt_core::time::TimestampUtils;
493    ///
494    /// let milliseconds = 1704110400000; // 2024-01-01 12:00:00 UTC
495    /// let seconds = TimestampUtils::ms_to_seconds(milliseconds);
496    /// assert_eq!(seconds, 1704110400);
497    /// ```
498    pub fn ms_to_seconds(ms: i64) -> i64 {
499        ms / 1000
500    }
501
502    /// Validate timestamp is within reasonable bounds
503    ///
504    /// Ensures the timestamp is:
505    /// - Not negative (valid Unix timestamp)
506    /// - Not too far in the future (before year 2100)
507    ///
508    /// # Arguments
509    ///
510    /// * `timestamp` - Timestamp in milliseconds to validate
511    ///
512    /// # Returns
513    ///
514    /// The validated timestamp if valid, or an error if invalid
515    ///
516    /// # Errors
517    ///
518    /// - `Error::InvalidRequest` if timestamp is negative
519    /// - `Error::InvalidRequest` if timestamp is too far in future
520    ///
521    /// # Example
522    ///
523    /// ```rust
524    /// use ccxt_core::time::TimestampUtils;
525    ///
526    /// // Valid timestamp
527    /// let valid = TimestampUtils::validate_timestamp(1704110400000).unwrap();
528    /// assert_eq!(valid, 1704110400000);
529    ///
530    /// // Invalid timestamp (negative)
531    /// let invalid = TimestampUtils::validate_timestamp(-1);
532    /// assert!(invalid.is_err());
533    /// ```
534    pub fn validate_timestamp(timestamp: i64) -> Result<i64> {
535        if timestamp < Self::UNIX_EPOCH_MS {
536            return Err(Error::invalid_request("Timestamp cannot be negative"));
537        }
538
539        if timestamp > Self::YEAR_2100_MS {
540            return Err(Error::invalid_request(
541                "Timestamp too far in future (after year 2100)",
542            ));
543        }
544
545        Ok(timestamp)
546    }
547
548    /// Parse timestamp from string (handles various formats)
549    ///
550    /// Attempts to parse a timestamp from string format, supporting:
551    /// - Integer strings (milliseconds): "1704110400000"
552    /// - Decimal strings (seconds with fractional part): "1704110400.123"
553    /// - Scientific notation: "1.7041104e12"
554    ///
555    /// # Arguments
556    ///
557    /// * `s` - String representation of timestamp
558    ///
559    /// # Returns
560    ///
561    /// Parsed and validated timestamp in milliseconds
562    ///
563    /// # Errors
564    ///
565    /// - `Error::Parse` if string cannot be parsed as number
566    /// - `Error::InvalidRequest` if parsed timestamp is invalid
567    ///
568    /// # Example
569    ///
570    /// ```rust
571    /// use ccxt_core::time::TimestampUtils;
572    ///
573    /// // Parse integer milliseconds
574    /// let ts1 = TimestampUtils::parse_timestamp("1704110400000").unwrap();
575    /// assert_eq!(ts1, 1704110400000);
576    ///
577    /// // Parse decimal seconds
578    /// let ts2 = TimestampUtils::parse_timestamp("1704110400.123").unwrap();
579    /// assert_eq!(ts2, 1704110400123);
580    /// ```
581    pub fn parse_timestamp(s: &str) -> Result<i64> {
582        if s.is_empty() {
583            return Err(Error::invalid_request("Empty timestamp string"));
584        }
585
586        // Try parsing as i64 first (milliseconds)
587        if let Ok(ts) = s.parse::<i64>() {
588            return Self::validate_timestamp(ts);
589        }
590
591        // Try parsing as f64 (seconds with fractional part)
592        if let Ok(ts_f64) = s.parse::<f64>()
593            && ts_f64.is_finite()
594        {
595            #[allow(clippy::cast_possible_truncation)]
596            let ts = (ts_f64 * 1000.0) as i64;
597            return Self::validate_timestamp(ts);
598        }
599
600        Err(Error::invalid_request(format!(
601            "Invalid timestamp format: {s}"
602        )))
603    }
604
605    /// Format timestamp as ISO 8601 string
606    ///
607    /// Converts a timestamp to ISO 8601 format with millisecond precision.
608    /// Always uses UTC timezone and includes milliseconds.
609    ///
610    /// # Arguments
611    ///
612    /// * `timestamp` - Timestamp in milliseconds since Unix epoch
613    ///
614    /// # Returns
615    ///
616    /// ISO 8601 formatted string (e.g., "2024-01-01T12:00:00.000Z")
617    ///
618    /// # Example
619    ///
620    /// ```rust
621    /// use ccxt_core::time::TimestampUtils;
622    ///
623    /// let timestamp = 1704110400000; // 2024-01-01 12:00:00 UTC
624    /// let iso_str = TimestampUtils::format_iso8601(timestamp).unwrap();
625    /// assert_eq!(iso_str, "2024-01-01T12:00:00.000Z");
626    /// ```
627    pub fn format_iso8601(timestamp: i64) -> Result<String> {
628        let validated = Self::validate_timestamp(timestamp)?;
629
630        let secs = validated / 1000;
631        #[allow(clippy::cast_possible_truncation)]
632        let nsecs = ((validated % 1000) * 1_000_000) as u32;
633
634        let datetime = DateTime::<Utc>::from_timestamp(secs, nsecs)
635            .ok_or_else(|| Error::invalid_request(format!("Invalid timestamp: {validated}")))?;
636
637        Ok(datetime.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string())
638    }
639
640    /// Check if a timestamp represents a reasonable date
641    ///
642    /// Validates that the timestamp represents a date between 1970 and 2100,
643    /// which covers all reasonable use cases for cryptocurrency trading.
644    ///
645    /// # Arguments
646    ///
647    /// * `timestamp` - Timestamp in milliseconds to check
648    ///
649    /// # Returns
650    ///
651    /// `true` if timestamp is reasonable, `false` otherwise
652    ///
653    /// # Example
654    ///
655    /// ```rust
656    /// use ccxt_core::time::TimestampUtils;
657    ///
658    /// assert!(TimestampUtils::is_reasonable_timestamp(1704110400000)); // 2024
659    /// assert!(!TimestampUtils::is_reasonable_timestamp(-1)); // Before 1970
660    /// ```
661    pub fn is_reasonable_timestamp(timestamp: i64) -> bool {
662        (Self::UNIX_EPOCH_MS..=Self::YEAR_2100_MS).contains(&timestamp)
663    }
664
665    /// Convert u64 to i64 with overflow checking
666    ///
667    /// This function is provided for migration support when converting from
668    /// old u64 timestamp code to new i64 timestamp code. It performs overflow
669    /// checking to ensure the conversion is safe.
670    ///
671    /// # Arguments
672    ///
673    /// * `timestamp` - u64 timestamp to convert
674    ///
675    /// # Returns
676    ///
677    /// Converted i64 timestamp if within valid range
678    ///
679    /// # Errors
680    ///
681    /// - `Error::InvalidRequest` if u64 value exceeds i64::MAX
682    ///
683    /// # Example
684    ///
685    /// ```rust
686    /// use ccxt_core::time::TimestampUtils;
687    ///
688    /// let old_timestamp: u64 = 1704110400000;
689    /// let new_timestamp = TimestampUtils::u64_to_i64(old_timestamp).unwrap();
690    /// assert_eq!(new_timestamp, 1704110400000i64);
691    /// ```
692    #[deprecated(since = "0.1.0", note = "Use i64 timestamps directly")]
693    pub fn u64_to_i64(timestamp: u64) -> Result<i64> {
694        if timestamp > i64::MAX as u64 {
695            return Err(Error::invalid_request(format!(
696                "Timestamp overflow: {timestamp} exceeds maximum i64 value"
697            )));
698        }
699        let converted = timestamp as i64;
700        Self::validate_timestamp(converted)
701    }
702
703    /// Convert i64 to u64 with underflow checking
704    ///
705    /// This function is provided for backward compatibility when interfacing
706    /// with legacy code that expects u64 timestamps. It performs underflow
707    /// checking to ensure the conversion is safe.
708    ///
709    /// # Arguments
710    ///
711    /// * `timestamp` - i64 timestamp to convert
712    ///
713    /// # Returns
714    ///
715    /// Converted u64 timestamp if non-negative
716    ///
717    /// # Errors
718    ///
719    /// - `Error::InvalidRequest` if i64 value is negative
720    ///
721    /// # Example
722    ///
723    /// ```rust
724    /// use ccxt_core::time::TimestampUtils;
725    ///
726    /// let new_timestamp: i64 = 1704110400000;
727    /// let old_timestamp = TimestampUtils::i64_to_u64(new_timestamp).unwrap();
728    /// assert_eq!(old_timestamp, 1704110400000u64);
729    /// ```
730    pub fn i64_to_u64(timestamp: i64) -> Result<u64> {
731        if timestamp < 0 {
732            return Err(Error::invalid_request(
733                "Cannot convert negative timestamp to u64",
734            ));
735        }
736        Ok(timestamp as u64)
737    }
738
739    /// Get timestamp for start of day (00:00:00 UTC)
740    ///
741    /// Given a timestamp, returns the timestamp for the start of that day in UTC.
742    ///
743    /// # Arguments
744    ///
745    /// * `timestamp` - Any timestamp within the target day
746    ///
747    /// # Returns
748    ///
749    /// Timestamp for 00:00:00 UTC of the same day
750    ///
751    /// # Example
752    ///
753    /// ```rust
754    /// use ccxt_core::time::TimestampUtils;
755    ///
756    /// let timestamp = 1704110400000; // 2024-01-01 12:00:00 UTC
757    /// let start_of_day = TimestampUtils::start_of_day(timestamp).unwrap();
758    /// // Should be 2024-01-01 00:00:00 UTC
759    /// ```
760    pub fn start_of_day(timestamp: i64) -> Result<i64> {
761        let validated = Self::validate_timestamp(timestamp)?;
762
763        let secs = validated / 1000;
764        let datetime = DateTime::<Utc>::from_timestamp(secs, 0)
765            .ok_or_else(|| Error::invalid_request(format!("Invalid timestamp: {validated}")))?;
766
767        let start_of_day = datetime
768            .date_naive()
769            .and_hms_opt(0, 0, 0)
770            .ok_or_else(|| Error::invalid_request("Failed to create start of day"))?;
771
772        let start_of_day_utc = Utc.from_utc_datetime(&start_of_day);
773        Ok(start_of_day_utc.timestamp_millis())
774    }
775
776    /// Get timestamp for end of day (23:59:59.999 UTC)
777    ///
778    /// Given a timestamp, returns the timestamp for the end of that day in UTC.
779    ///
780    /// # Arguments
781    ///
782    /// * `timestamp` - Any timestamp within the target day
783    ///
784    /// # Returns
785    ///
786    /// Timestamp for 23:59:59.999 UTC of the same day
787    ///
788    /// # Example
789    ///
790    /// ```rust
791    /// use ccxt_core::time::TimestampUtils;
792    ///
793    /// let timestamp = 1704110400000; // 2024-01-01 12:00:00 UTC
794    /// let end_of_day = TimestampUtils::end_of_day(timestamp).unwrap();
795    /// // Should be 2024-01-01 23:59:59.999 UTC
796    /// ```
797    pub fn end_of_day(timestamp: i64) -> Result<i64> {
798        let validated = Self::validate_timestamp(timestamp)?;
799
800        let secs = validated / 1000;
801        let datetime = DateTime::<Utc>::from_timestamp(secs, 0)
802            .ok_or_else(|| Error::invalid_request(format!("Invalid timestamp: {validated}")))?;
803
804        let end_of_day = datetime
805            .date_naive()
806            .and_hms_milli_opt(23, 59, 59, 999)
807            .ok_or_else(|| Error::invalid_request("Failed to create end of day"))?;
808
809        let end_of_day_utc = Utc.from_utc_datetime(&end_of_day);
810        Ok(end_of_day_utc.timestamp_millis())
811    }
812}
813
814/// Extension trait for Option<u64> to Option<i64> conversion
815///
816/// This trait provides convenient methods for converting Option<u64> timestamps
817/// to Option<i64> timestamps during migration. It handles the conversion and
818/// validation in a single operation.
819///
820/// # Example
821///
822/// ```rust
823/// use ccxt_core::time::TimestampConversion;
824///
825/// let old_timestamp: Option<u64> = Some(1704110400000);
826/// let new_timestamp = old_timestamp.to_i64().unwrap();
827/// assert_eq!(new_timestamp, Some(1704110400000i64));
828///
829/// let none_timestamp: Option<u64> = None;
830/// let converted = none_timestamp.to_i64().unwrap();
831/// assert_eq!(converted, None);
832/// ```
833pub trait TimestampConversion {
834    /// Convert Option<u64> to Option<i64> with validation
835    fn to_i64(self) -> Result<Option<i64>>;
836}
837
838impl TimestampConversion for Option<u64> {
839    #[allow(deprecated)]
840    fn to_i64(self) -> Result<Option<i64>> {
841        match self {
842            Some(ts) => Ok(Some(TimestampUtils::u64_to_i64(ts)?)),
843            None => Ok(None),
844        }
845    }
846}
847
848#[cfg(test)]
849mod tests {
850    use super::*;
851
852    #[test]
853    fn test_milliseconds() {
854        let now = milliseconds();
855        assert!(now > 1_600_000_000_000); // After 2020
856        assert!(now < 2_000_000_000_000); // Before 2033
857    }
858
859    #[test]
860    fn test_seconds() {
861        let now = seconds();
862        assert!(now > 1_600_000_000); // After 2020
863        assert!(now < 2_000_000_000); // Before 2033
864    }
865
866    #[test]
867    fn test_microseconds() {
868        let now = microseconds();
869        assert!(now > 1_600_000_000_000_000); // After 2020
870    }
871
872    #[test]
873    fn test_iso8601() {
874        let timestamp = 1704110400000; // 2024-01-01 12:00:00 UTC
875        let result = iso8601(timestamp).unwrap();
876        assert_eq!(result, "2024-01-01T12:00:00.000Z");
877    }
878
879    #[test]
880    fn test_iso8601_with_millis() {
881        let timestamp = 1704110400123; // 2024-01-01 12:00:00.123 UTC
882        let result = iso8601(timestamp).unwrap();
883        assert_eq!(result, "2024-01-01T12:00:00.123Z");
884    }
885
886    #[test]
887    fn test_iso8601_invalid() {
888        let result = iso8601(-1);
889        assert!(result.is_err());
890    }
891
892    #[test]
893    fn test_parse_date_iso8601() {
894        let result = parse_date("2024-01-01T12:00:00.000Z").unwrap();
895        assert_eq!(result, 1704110400000);
896    }
897
898    #[test]
899    fn test_parse_date_space_separated() {
900        let result = parse_date("2024-01-01 12:00:00").unwrap();
901        assert_eq!(result, 1704110400000);
902    }
903
904    #[test]
905    fn test_parse_date_without_timezone() {
906        let result = parse_date("2024-01-01T12:00:00.389").unwrap();
907        assert_eq!(result, 1704110400389);
908    }
909
910    #[test]
911    fn test_parse_iso8601() {
912        let result = parse_iso8601("2024-01-01T12:00:00.000Z").unwrap();
913        assert_eq!(result, 1704110400000);
914    }
915
916    #[test]
917    fn test_parse_iso8601_with_offset() {
918        let result = parse_iso8601("2024-01-01T12:00:00+00:00").unwrap();
919        assert_eq!(result, 1704110400000);
920    }
921
922    #[test]
923    fn test_parse_iso8601_space_separated() {
924        let result = parse_iso8601("2024-01-01 12:00:00.389").unwrap();
925        assert_eq!(result, 1704110400389);
926    }
927
928    #[test]
929    fn test_ymdhms_default_separator() {
930        let timestamp = 1704110400000;
931        let result = ymdhms(timestamp, None).unwrap();
932        assert_eq!(result, "2024-01-01 12:00:00");
933    }
934
935    #[test]
936    fn test_ymdhms_custom_separator() {
937        let timestamp = 1704110400000;
938        let result = ymdhms(timestamp, Some("T")).unwrap();
939        assert_eq!(result, "2024-01-01T12:00:00");
940    }
941
942    #[test]
943    fn test_yyyymmdd_default_separator() {
944        let timestamp = 1704110400000;
945        let result = yyyymmdd(timestamp, None).unwrap();
946        assert_eq!(result, "2024-01-01");
947    }
948
949    #[test]
950    fn test_yyyymmdd_custom_separator() {
951        let timestamp = 1704110400000;
952        let result = yyyymmdd(timestamp, Some("/")).unwrap();
953        assert_eq!(result, "2024/01/01");
954    }
955
956    #[test]
957    fn test_yymmdd_no_separator() {
958        let timestamp = 1704110400000;
959        let result = yymmdd(timestamp, None).unwrap();
960        assert_eq!(result, "240101");
961    }
962
963    #[test]
964    fn test_yymmdd_with_separator() {
965        let timestamp = 1704110400000;
966        let result = yymmdd(timestamp, Some("-")).unwrap();
967        assert_eq!(result, "24-01-01");
968    }
969
970    #[test]
971    fn test_ymd_alias() {
972        let timestamp = 1704110400000;
973        let result = ymd(timestamp, None).unwrap();
974        assert_eq!(result, "2024-01-01");
975    }
976
977    #[test]
978    fn test_round_trip() {
979        let original = 1704110400000;
980        let iso_str = iso8601(original).unwrap();
981        let parsed = parse_date(&iso_str).unwrap();
982        assert_eq!(original, parsed);
983    }
984
985    // ==================== TimestampUtils Tests ====================
986
987    #[test]
988    fn test_timestamp_utils_now_ms() {
989        let now = TimestampUtils::now_ms();
990        assert!(now > 1_600_000_000_000); // After 2020
991        assert!(now < 2_000_000_000_000); // Before 2033
992    }
993
994    #[test]
995    fn test_timestamp_utils_seconds_to_ms() {
996        let seconds = 1704110400; // 2024-01-01 12:00:00 UTC
997        let milliseconds = TimestampUtils::seconds_to_ms(seconds);
998        assert_eq!(milliseconds, 1704110400000);
999    }
1000
1001    #[test]
1002    fn test_timestamp_utils_ms_to_seconds() {
1003        let milliseconds = 1704110400000; // 2024-01-01 12:00:00 UTC
1004        let seconds = TimestampUtils::ms_to_seconds(milliseconds);
1005        assert_eq!(seconds, 1704110400);
1006    }
1007
1008    #[test]
1009    fn test_timestamp_utils_validate_timestamp_valid() {
1010        let valid_timestamp = 1704110400000; // 2024-01-01 12:00:00 UTC
1011        let result = TimestampUtils::validate_timestamp(valid_timestamp).unwrap();
1012        assert_eq!(result, valid_timestamp);
1013    }
1014
1015    #[test]
1016    fn test_timestamp_utils_validate_timestamp_negative() {
1017        let result = TimestampUtils::validate_timestamp(-1);
1018        assert!(result.is_err());
1019        assert!(result.unwrap_err().to_string().contains("negative"));
1020    }
1021
1022    #[test]
1023    fn test_timestamp_utils_validate_timestamp_too_far_future() {
1024        let far_future = TimestampUtils::YEAR_2100_MS + 1;
1025        let result = TimestampUtils::validate_timestamp(far_future);
1026        assert!(result.is_err());
1027        assert!(
1028            result
1029                .unwrap_err()
1030                .to_string()
1031                .contains("too far in future")
1032        );
1033    }
1034
1035    #[test]
1036    fn test_timestamp_utils_parse_timestamp_integer() {
1037        let result = TimestampUtils::parse_timestamp("1704110400000").unwrap();
1038        assert_eq!(result, 1704110400000);
1039    }
1040
1041    #[test]
1042    fn test_timestamp_utils_parse_timestamp_decimal() {
1043        let result = TimestampUtils::parse_timestamp("1704110400.123").unwrap();
1044        assert_eq!(result, 1704110400123);
1045    }
1046
1047    #[test]
1048    fn test_timestamp_utils_parse_timestamp_invalid() {
1049        let result = TimestampUtils::parse_timestamp("invalid");
1050        assert!(result.is_err());
1051    }
1052
1053    #[test]
1054    fn test_timestamp_utils_parse_timestamp_empty() {
1055        let result = TimestampUtils::parse_timestamp("");
1056        assert!(result.is_err());
1057    }
1058
1059    #[test]
1060    fn test_timestamp_utils_format_iso8601() {
1061        let timestamp = 1704110400000; // 2024-01-01 12:00:00 UTC
1062        let result = TimestampUtils::format_iso8601(timestamp).unwrap();
1063        assert_eq!(result, "2024-01-01T12:00:00.000Z");
1064    }
1065
1066    #[test]
1067    fn test_timestamp_utils_format_iso8601_with_millis() {
1068        let timestamp = 1704110400123; // 2024-01-01 12:00:00.123 UTC
1069        let result = TimestampUtils::format_iso8601(timestamp).unwrap();
1070        assert_eq!(result, "2024-01-01T12:00:00.123Z");
1071    }
1072
1073    #[test]
1074    fn test_timestamp_utils_is_reasonable_timestamp() {
1075        assert!(TimestampUtils::is_reasonable_timestamp(1704110400000)); // 2024
1076        assert!(TimestampUtils::is_reasonable_timestamp(0)); // Unix epoch
1077        assert!(!TimestampUtils::is_reasonable_timestamp(-1)); // Before epoch
1078        assert!(!TimestampUtils::is_reasonable_timestamp(
1079            TimestampUtils::YEAR_2100_MS + 1
1080        )); // Too far future
1081    }
1082
1083    #[test]
1084    #[allow(deprecated)]
1085    fn test_timestamp_utils_u64_to_i64_valid() {
1086        let old_timestamp: u64 = 1704110400000;
1087        let result = TimestampUtils::u64_to_i64(old_timestamp).unwrap();
1088        assert_eq!(result, 1704110400000i64);
1089    }
1090
1091    #[test]
1092    #[allow(deprecated)]
1093    fn test_timestamp_utils_u64_to_i64_overflow() {
1094        let overflow_timestamp = u64::MAX;
1095        let result = TimestampUtils::u64_to_i64(overflow_timestamp);
1096        assert!(result.is_err());
1097        assert!(result.unwrap_err().to_string().contains("overflow"));
1098    }
1099
1100    #[test]
1101    #[allow(deprecated)]
1102    fn test_timestamp_utils_i64_to_u64_valid() {
1103        let new_timestamp: i64 = 1704110400000;
1104        let result = TimestampUtils::i64_to_u64(new_timestamp).unwrap();
1105        assert_eq!(result, 1704110400000u64);
1106    }
1107
1108    #[test]
1109    #[allow(deprecated)]
1110    fn test_timestamp_utils_i64_to_u64_negative() {
1111        let negative_timestamp: i64 = -1;
1112        let result = TimestampUtils::i64_to_u64(negative_timestamp);
1113        assert!(result.is_err());
1114        assert!(result.unwrap_err().to_string().contains("negative"));
1115    }
1116
1117    #[test]
1118    fn test_timestamp_utils_start_of_day() {
1119        let timestamp = 1704110400000; // 2024-01-01 12:00:00 UTC
1120        let start_of_day = TimestampUtils::start_of_day(timestamp).unwrap();
1121
1122        // Should be 2024-01-01 00:00:00 UTC = 1704067200000
1123        let expected = 1704067200000;
1124        assert_eq!(start_of_day, expected);
1125    }
1126
1127    #[test]
1128    fn test_timestamp_utils_end_of_day() {
1129        let timestamp = 1704110400000; // 2024-01-01 12:00:00 UTC
1130        let end_of_day = TimestampUtils::end_of_day(timestamp).unwrap();
1131
1132        // Should be 2024-01-01 23:59:59.999 UTC = 1704153599999
1133        let expected = 1704153599999;
1134        assert_eq!(end_of_day, expected);
1135    }
1136
1137    // ==================== TimestampConversion Tests ====================
1138
1139    #[test]
1140    fn test_timestamp_conversion_some() {
1141        let old_timestamp: Option<u64> = Some(1704110400000);
1142        let result = old_timestamp.to_i64().unwrap();
1143        assert_eq!(result, Some(1704110400000i64));
1144    }
1145
1146    #[test]
1147    fn test_timestamp_conversion_none() {
1148        let old_timestamp: Option<u64> = None;
1149        let result = old_timestamp.to_i64().unwrap();
1150        assert_eq!(result, None);
1151    }
1152
1153    #[test]
1154    fn test_timestamp_conversion_overflow() {
1155        let old_timestamp: Option<u64> = Some(u64::MAX);
1156        let result = old_timestamp.to_i64();
1157        assert!(result.is_err());
1158    }
1159
1160    // ==================== Integration Tests ====================
1161
1162    #[test]
1163    fn test_timestamp_round_trip_with_validation() {
1164        let original = 1704110400000;
1165
1166        // Validate original
1167        let validated = TimestampUtils::validate_timestamp(original).unwrap();
1168        assert_eq!(validated, original);
1169
1170        // Format to ISO 8601
1171        let iso_str = TimestampUtils::format_iso8601(validated).unwrap();
1172
1173        // Parse back
1174        let parsed = parse_date(&iso_str).unwrap();
1175        assert_eq!(parsed, original);
1176    }
1177
1178    #[test]
1179    fn test_migration_workflow() {
1180        // Simulate migration from u64 to i64
1181        let old_timestamp: u64 = 1704110400000;
1182
1183        // Convert to i64
1184        #[allow(deprecated)]
1185        let new_timestamp = TimestampUtils::u64_to_i64(old_timestamp).unwrap();
1186
1187        // Validate
1188        let validated = TimestampUtils::validate_timestamp(new_timestamp).unwrap();
1189
1190        // Use in formatting
1191        let formatted = TimestampUtils::format_iso8601(validated).unwrap();
1192        assert_eq!(formatted, "2024-01-01T12:00:00.000Z");
1193
1194        // Convert back for legacy code if needed
1195        #[allow(deprecated)]
1196        let back_to_u64 = TimestampUtils::i64_to_u64(validated).unwrap();
1197        assert_eq!(back_to_u64, old_timestamp);
1198    }
1199
1200    #[test]
1201    fn test_edge_cases() {
1202        // Test Unix epoch
1203        let epoch = 0i64;
1204        let validated = TimestampUtils::validate_timestamp(epoch).unwrap();
1205        assert_eq!(validated, epoch);
1206
1207        // Test year 2100 boundary
1208        let year_2100 = TimestampUtils::YEAR_2100_MS;
1209        let validated = TimestampUtils::validate_timestamp(year_2100).unwrap();
1210        assert_eq!(validated, year_2100);
1211
1212        // Test just over year 2100 boundary
1213        let over_2100 = TimestampUtils::YEAR_2100_MS + 1;
1214        let result = TimestampUtils::validate_timestamp(over_2100);
1215        assert!(result.is_err());
1216    }
1217
1218    #[test]
1219    fn test_consistency_between_functions() {
1220        let timestamp = 1704110400000;
1221
1222        // Test that all formatting functions use the same validation
1223        let ymdhms_result = ymdhms(timestamp, None);
1224        let yyyymmdd_result = yyyymmdd(timestamp, None);
1225        let yymmdd_result = yymmdd(timestamp, None);
1226        let iso8601_result = iso8601(timestamp);
1227
1228        assert!(ymdhms_result.is_ok());
1229        assert!(yyyymmdd_result.is_ok());
1230        assert!(yymmdd_result.is_ok());
1231        assert!(iso8601_result.is_ok());
1232
1233        // Test that all fail for invalid timestamps
1234        let invalid = -1i64;
1235        assert!(ymdhms(invalid, None).is_err());
1236        assert!(yyyymmdd(invalid, None).is_err());
1237        assert!(yymmdd(invalid, None).is_err());
1238        assert!(iso8601(invalid).is_err());
1239    }
1240}