RedEnginePress logo
RedEnginePress
AlgorithmsLanguagesPlaygroundAbout

Time

P
//! # Time Unit Conversion
//!
//! A unit of time is any particular time interval, used as a standard way of
//! measuring or expressing duration. The base unit of time in the International
//! System of Units (SI), and by extension most of the Western world, is the second,
//! defined as about 9 billion oscillations of the caesium atom.
//!
//! More information: <https://en.wikipedia.org/wiki/Unit_of_time>

/// Supported time units for conversion
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TimeUnit {
    Seconds,
    Minutes,
    Hours,
    Days,
    Weeks,
    Months,
    Years,
}

impl TimeUnit {
    /// Returns the value of the time unit in seconds
    fn to_seconds(self) -> f64 {
        match self {
            TimeUnit::Seconds => 1.0,
            TimeUnit::Minutes => 60.0,
            TimeUnit::Hours => 3600.0,
            TimeUnit::Days => 86400.0,
            TimeUnit::Weeks => 604800.0,
            TimeUnit::Months => 2_629_800.0, // Approximate value
            TimeUnit::Years => 31_557_600.0, // Approximate value
        }
    }

    /// Parse a string into a TimeUnit (case-insensitive)
    fn from_str(s: &str) -> Result<Self, String> {
        match s.to_lowercase().as_str() {
            "seconds" => Ok(TimeUnit::Seconds),
            "minutes" => Ok(TimeUnit::Minutes),
            "hours" => Ok(TimeUnit::Hours),
            "days" => Ok(TimeUnit::Days),
            "weeks" => Ok(TimeUnit::Weeks),
            "months" => Ok(TimeUnit::Months),
            "years" => Ok(TimeUnit::Years),
            _ => Err(format!(
                "Invalid unit {s} is not in seconds, minutes, hours, days, weeks, months, years."
            )),
        }
    }
}

/// Convert time from one unit to another
///
/// # Arguments
///
/// * `time_value` - The time value to convert (must be non-negative)
/// * `unit_from` - The source unit (case-insensitive)
/// * `unit_to` - The target unit (case-insensitive)
///
/// # Returns
///
/// Returns the converted time value rounded to 3 decimal places
///
/// # Errors
///
/// Returns an error if:
/// * `time_value` is negative or not a valid number
/// * `unit_from` or `unit_to` is not a valid time unit
pub fn convert_time(time_value: f64, unit_from: &str, unit_to: &str) -> Result<f64, String> {
    // Validate that time_value is non-negative
    if time_value < 0.0 || time_value.is_nan() || time_value.is_infinite() {
        return Err("'time_value' must be a non-negative number.".to_string());
    }

    // Parse units
    let from_unit = TimeUnit::from_str(unit_from)?;
    let to_unit = TimeUnit::from_str(unit_to)?;

    // Convert: time_value -> seconds -> target unit
    let seconds = time_value * from_unit.to_seconds();
    let result = seconds / to_unit.to_seconds();

    // Round to 3 decimal places
    Ok((result * 1000.0).round() / 1000.0)
}

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

    #[test]
    fn test_seconds_to_hours() {
        assert_eq!(convert_time(3600.0, "seconds", "hours").unwrap(), 1.0);
    }

    #[test]
    fn test_case_insensitive() {
        assert_eq!(convert_time(3500.0, "Seconds", "Hours").unwrap(), 0.972);
        assert_eq!(convert_time(1.0, "DaYs", "hours").unwrap(), 24.0);
        assert_eq!(convert_time(120.0, "minutes", "SeCoNdS").unwrap(), 7200.0);
    }

    #[test]
    fn test_weeks_to_days() {
        assert_eq!(convert_time(2.0, "WEEKS", "days").unwrap(), 14.0);
    }

    #[test]
    fn test_hours_to_minutes() {
        assert_eq!(convert_time(0.5, "hours", "MINUTES").unwrap(), 30.0);
    }

    #[test]
    fn test_days_to_months() {
        assert_eq!(convert_time(360.0, "days", "months").unwrap(), 11.828);
    }

    #[test]
    fn test_months_to_years() {
        assert_eq!(convert_time(360.0, "months", "years").unwrap(), 30.0);
    }

    #[test]
    fn test_years_to_seconds() {
        assert_eq!(convert_time(1.0, "years", "seconds").unwrap(), 31_557_600.0);
    }

    #[test]
    fn test_negative_value() {
        let result = convert_time(-3600.0, "seconds", "hours");
        assert!(result.is_err());
        assert_eq!(
            result.unwrap_err(),
            "'time_value' must be a non-negative number."
        );
    }

    #[test]
    fn test_invalid_from_unit() {
        let result = convert_time(1.0, "cool", "century");
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("Invalid unit cool"));
    }

    #[test]
    fn test_invalid_to_unit() {
        let result = convert_time(1.0, "seconds", "hot");
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("Invalid unit hot"));
    }

    #[test]
    fn test_zero_value() {
        assert_eq!(convert_time(0.0, "hours", "minutes").unwrap(), 0.0);
    }

    #[test]
    fn test_same_unit() {
        assert_eq!(convert_time(100.0, "seconds", "seconds").unwrap(), 100.0);
    }
}