spacecell 0.1.0

Datetime library with ISO8601/RFC3339 parsing, calendar arithmetic, and business day calculations
Documentation
//! # **Convert Module** - *Time unit conversions*
//!
//! Efficient conversion between different time units with overflow checking.

use crate::time_units::TimeUnit;

/// Conversion errors
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConversionError {
    Overflow,
    PrecisionLoss { from: String, to: String },
}

impl std::fmt::Display for ConversionError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ConversionError::Overflow => write!(f, "Timestamp conversion overflow"),
            ConversionError::PrecisionLoss { from, to } => {
                write!(f, "Precision loss converting from {} to {}", from, to)
            }
        }
    }
}

impl std::error::Error for ConversionError {}

/// Convert timestamp between time units
///
/// # Arguments
/// - `value`: Timestamp in source unit
/// - `from_unit`: Source time unit
/// - `to_unit`: Target time unit
///
/// # Returns
/// Converted timestamp or ConversionError
pub fn convert_timestamp(
    value: i64,
    from_unit: TimeUnit,
    to_unit: TimeUnit,
) -> Result<i64, ConversionError> {
    if from_unit == to_unit {
        return Ok(value);
    }

    // Convert to nanoseconds as intermediate representation
    let nanos = match from_unit {
        TimeUnit::Seconds => value
            .checked_mul(1_000_000_000)
            .ok_or(ConversionError::Overflow)?,
        TimeUnit::Milliseconds => value
            .checked_mul(1_000_000)
            .ok_or(ConversionError::Overflow)?,
        TimeUnit::Microseconds => value.checked_mul(1_000).ok_or(ConversionError::Overflow)?,
        TimeUnit::Nanoseconds => value,
        TimeUnit::Days => value
            .checked_mul(86_400_000_000_000)
            .ok_or(ConversionError::Overflow)?,
    };

    // Convert from nanoseconds to target unit
    let result = match to_unit {
        TimeUnit::Seconds => nanos
            .checked_div(1_000_000_000)
            .ok_or(ConversionError::Overflow)?,
        TimeUnit::Milliseconds => nanos
            .checked_div(1_000_000)
            .ok_or(ConversionError::Overflow)?,
        TimeUnit::Microseconds => nanos.checked_div(1_000).ok_or(ConversionError::Overflow)?,
        TimeUnit::Nanoseconds => nanos,
        TimeUnit::Days => nanos
            .checked_div(86_400_000_000_000)
            .ok_or(ConversionError::Overflow)?,
    };

    Ok(result)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_convert_same_unit() {
        let result = convert_timestamp(1000, TimeUnit::Seconds, TimeUnit::Seconds).unwrap();
        assert_eq!(result, 1000);
    }

    #[test]
    fn test_convert_seconds_to_millis() {
        let result = convert_timestamp(1, TimeUnit::Seconds, TimeUnit::Milliseconds).unwrap();
        assert_eq!(result, 1_000);
    }

    #[test]
    fn test_convert_millis_to_seconds() {
        let result = convert_timestamp(1_000, TimeUnit::Milliseconds, TimeUnit::Seconds).unwrap();
        assert_eq!(result, 1);
    }

    #[test]
    fn test_convert_seconds_to_nanos() {
        let result = convert_timestamp(1, TimeUnit::Seconds, TimeUnit::Nanoseconds).unwrap();
        assert_eq!(result, 1_000_000_000);
    }

    #[test]
    fn test_convert_nanos_to_seconds() {
        let result =
            convert_timestamp(1_000_000_000, TimeUnit::Nanoseconds, TimeUnit::Seconds).unwrap();
        assert_eq!(result, 1);
    }

    #[test]
    fn test_convert_days_to_seconds() {
        let result = convert_timestamp(1, TimeUnit::Days, TimeUnit::Seconds).unwrap();
        assert_eq!(result, 86_400);
    }

    #[test]
    fn test_convert_seconds_to_days() {
        let result = convert_timestamp(86_400, TimeUnit::Seconds, TimeUnit::Days).unwrap();
        assert_eq!(result, 1);
    }

    #[test]
    fn test_convert_precision_loss() {
        // 1.5 seconds as milliseconds
        let result = convert_timestamp(1_500, TimeUnit::Milliseconds, TimeUnit::Seconds).unwrap();
        assert_eq!(result, 1); // Truncates
    }

    #[test]
    fn test_convert_all_pairs() {
        // Test roundtrip conversions where possible
        let units = vec![
            TimeUnit::Days,
            TimeUnit::Seconds,
            TimeUnit::Milliseconds,
            TimeUnit::Microseconds,
            TimeUnit::Nanoseconds,
        ];

        for from_unit in &units {
            for to_unit in &units {
                // Use a value that won't lose precision
                let value = match from_unit {
                    TimeUnit::Days => 1,
                    TimeUnit::Seconds => 86_400,
                    TimeUnit::Milliseconds => 86_400_000,
                    TimeUnit::Microseconds => 86_400_000_000,
                    TimeUnit::Nanoseconds => 86_400_000_000_000,
                };

                let result = convert_timestamp(value, *from_unit, *to_unit);
                assert!(result.is_ok(), "Failed to convert from {:?} to {:?}", from_unit, to_unit);
            }
        }
    }
}