spacecell 0.1.0

Datetime library with ISO8601/RFC3339 parsing, calendar arithmetic, and business day calculations
Documentation
//! # **Format Module** - *ISO8601/RFC3339 datetime formatting*
//!
//! Fast datetime formatting with minimal allocations.

use super::components::extract_components;
use crate::time_units::TimeUnit;

/// Format timestamp as ISO8601 datetime string
///
/// Format: YYYY-MM-DDTHH:MM:SS[.fff]Z
///
/// # Arguments
/// - `timestamp`: Raw timestamp value
/// - `time_unit`: Unit of the timestamp
///
/// # Returns
/// ISO8601 formatted string
pub fn format_iso8601(timestamp: i64, time_unit: TimeUnit) -> String {
    let comp = extract_components(timestamp, time_unit);

    match time_unit {
        TimeUnit::Seconds => format!(
            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
            comp.year, comp.month, comp.day, comp.hour, comp.minute, comp.second
        ),
        TimeUnit::Milliseconds => format!(
            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
            comp.year,
            comp.month,
            comp.day,
            comp.hour,
            comp.minute,
            comp.second,
            comp.nanosecond / 1_000_000
        ),
        TimeUnit::Microseconds => format!(
            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z",
            comp.year,
            comp.month,
            comp.day,
            comp.hour,
            comp.minute,
            comp.second,
            comp.nanosecond / 1_000
        ),
        TimeUnit::Nanoseconds => format!(
            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}Z",
            comp.year, comp.month, comp.day, comp.hour, comp.minute, comp.second, comp.nanosecond
        ),
        TimeUnit::Days => format!("{:04}-{:02}-{:02}", comp.year, comp.month, comp.day),
    }
}

/// Format timestamp as RFC3339 datetime string (alias for ISO8601)
pub fn format_rfc3339(timestamp: i64, time_unit: TimeUnit) -> String {
    format_iso8601(timestamp, time_unit)
}

/// Format timestamp as date only (YYYY-MM-DD)
pub fn format_date_only(timestamp: i64, time_unit: TimeUnit) -> String {
    let comp = extract_components(timestamp, time_unit);
    format!("{:04}-{:02}-{:02}", comp.year, comp.month, comp.day)
}

/// Format timestamp as time only (HH:MM:SS)
pub fn format_time_only(timestamp: i64, time_unit: TimeUnit) -> String {
    let comp = extract_components(timestamp, time_unit);

    match time_unit {
        TimeUnit::Seconds => format!(
            "{:02}:{:02}:{:02}",
            comp.hour, comp.minute, comp.second
        ),
        TimeUnit::Milliseconds => format!(
            "{:02}:{:02}:{:02}.{:03}",
            comp.hour,
            comp.minute,
            comp.second,
            comp.nanosecond / 1_000_000
        ),
        TimeUnit::Microseconds => format!(
            "{:02}:{:02}:{:02}.{:06}",
            comp.hour,
            comp.minute,
            comp.second,
            comp.nanosecond / 1_000
        ),
        TimeUnit::Nanoseconds => format!(
            "{:02}:{:02}:{:02}.{:09}",
            comp.hour, comp.minute, comp.second, comp.nanosecond
        ),
        TimeUnit::Days => String::from("00:00:00"),
    }
}

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

    #[test]
    fn test_format_iso8601_epoch() {
        assert_eq!(
            format_iso8601(0, TimeUnit::Seconds),
            "1970-01-01T00:00:00Z"
        );
    }

    #[test]
    fn test_format_iso8601_with_time() {
        // 2000-01-01 12:34:56
        let ts = 946730096;
        assert_eq!(
            format_iso8601(ts, TimeUnit::Seconds),
            "2000-01-01T12:34:56Z"
        );
    }

    #[test]
    fn test_format_iso8601_milliseconds() {
        assert_eq!(
            format_iso8601(123, TimeUnit::Milliseconds),
            "1970-01-01T00:00:00.123Z"
        );
    }

    #[test]
    fn test_format_iso8601_microseconds() {
        assert_eq!(
            format_iso8601(123456, TimeUnit::Microseconds),
            "1970-01-01T00:00:00.123456Z"
        );
    }

    #[test]
    fn test_format_iso8601_nanoseconds() {
        assert_eq!(
            format_iso8601(123456789, TimeUnit::Nanoseconds),
            "1970-01-01T00:00:00.123456789Z"
        );
    }

    #[test]
    fn test_format_date_only() {
        assert_eq!(format_date_only(0, TimeUnit::Seconds), "1970-01-01");
        assert_eq!(format_date_only(946684800, TimeUnit::Seconds), "2000-01-01");
    }

    #[test]
    fn test_format_time_only() {
        assert_eq!(format_time_only(0, TimeUnit::Seconds), "00:00:00");

        // 12:34:56
        let ts = 12 * 3600 + 34 * 60 + 56;
        assert_eq!(format_time_only(ts, TimeUnit::Seconds), "12:34:56");
    }

    #[test]
    fn test_roundtrip() {
        use super::super::parse::parse_iso8601;

        let test_cases = vec![
            "1970-01-01T00:00:00Z",
            "2000-01-01T12:34:56Z",
            "2020-02-29T23:59:59Z",
        ];

        for expected in test_cases {
            let (ts, unit) = parse_iso8601(expected).unwrap();
            let formatted = format_iso8601(ts, unit);
            assert_eq!(formatted, expected);
        }
    }
}