rstime 0.1.0

A zero-dependency Rust time library providing date, time, datetime types with formatting, parsing, Unix timestamps, and clock functionality.
Documentation
//! Format module
//!
//! Provides `TimeFormat` trait and predefined format constants for
//! formatting date/time values using a `{TOKEN}` system.

use crate::date::Date;
use crate::datetime::DateTime;
use crate::time::Time;

/// Trait for formatting date/time values with custom format strings
///
/// Uses a `{TOKEN}` format string syntax. Supported tokens:
///
/// | Token | Description | Example |
/// |-------|-------------|---------|
/// | `{YYYY}` | 4-digit year | `2026` |
/// | `{YY}` | 2-digit year | `26` |
/// | `{MM}` | 2-digit month | `05` |
/// | `{M}` | Month (no padding) | `5` |
/// | `{DD}` | 2-digit day | `10` |
/// | `{D}` | Day (no padding) | `10` |
/// | `{HH}` | 24-hour | `14` |
/// | `{H}` | 24-hour (no padding) | `5` |
/// | `{hh}` | 12-hour | `02` |
/// | `{h}` | 12-hour (no padding) | `2` |
/// | `{mm}` | 2-digit minute | `05` |
/// | `{m}` | Minute (no padding) | `5` |
/// | `{ss}` | 2-digit second | `09` |
/// | `{s}` | Second (no padding) | `9` |
/// | `{SSS}` | 3-digit millisecond | `037` |
/// | `{W}` | Short weekday | `Sun` |
/// | `{WW}` | Full weekday | `Sunday` |
/// | `{AMPM}` | Uppercase AM/PM | `PM` |
/// | `{ampm}` | Lowercase am/pm | `pm` |
///
/// # Examples
///
/// ```rust
/// use rstime::{DateTime, TimeFormat, ISO8601};
///
/// let dt = DateTime::from_ymd_hms_milli(2026, 5, 10, 14, 5, 9, 37);
/// assert_eq!(dt.format(ISO8601), "2026-05-10T14:05:09");
/// ```
pub trait TimeFormat {
    /// Format the value using the given format string
    fn format(&self, fmt: &str) -> String;
}

impl TimeFormat for DateTime {
    fn format(&self, fmt: &str) -> String {
        format_datetime(self, fmt)
    }
}

impl TimeFormat for Date {
    fn format(&self, fmt: &str) -> String {
        let dt = DateTime::new(
            *self,
            Time {
                hour: 0,
                minute: 0,
                second: 0,
                millisecond: 0,
            },
        );
        format_datetime(&dt, fmt)
    }
}

impl TimeFormat for Time {
    fn format(&self, fmt: &str) -> String {
        format_time(self, fmt)
    }
}

fn format_datetime(dt: &DateTime, fmt: &str) -> String {
    let mut result = String::new();
    let mut i = 0;
    let chars: Vec<char> = fmt.chars().collect();

    while i < chars.len() {
        if chars[i] == '{' {
            let mut end = i + 1;
            while end < chars.len() && chars[end] != '}' {
                end += 1;
            }
            if end < chars.len() && end > i + 1 {
                let token: String = chars[i + 1..end].iter().collect();
                result.push_str(&resolve_token_datetime(dt, &token));
                i = end + 1;
                continue;
            }
        }
        result.push(chars[i]);
        i += 1;
    }

    result
}

fn format_time(t: &Time, fmt: &str) -> String {
    let dt = DateTime::new(Date::new(2000, 1, 1), *t);
    format_datetime(&dt, fmt)
}

fn resolve_token_datetime(dt: &DateTime, token: &str) -> String {
    let t = &dt.time;
    let d = &dt.date;

    match token {
        "YYYY" => format!("{:04}", d.year),
        "YY" => format!("{:02}", d.year % 100),
        "MM" => format!("{:02}", d.month),
        "M" => format!("{}", d.month),
        "DD" => format!("{:02}", d.day),
        "D" => format!("{}", d.day),
        "HH" => format!("{:02}", t.hour),
        "H" => format!("{}", t.hour),
        "hh" => {
            let (h, _) = t.hour12();
            format!("{:02}", h)
        }
        "h" => {
            let (h, _) = t.hour12();
            format!("{}", h)
        }
        "mm" => format!("{:02}", t.minute),
        "m" => format!("{}", t.minute),
        "ss" => format!("{:02}", t.second),
        "s" => format!("{}", t.second),
        "SSS" => format!("{:03}", t.millisecond),
        "W" => {
            let wd = d.weekday();
            match wd {
                crate::date::Weekday::Monday => "Mon",
                crate::date::Weekday::Tuesday => "Tue",
                crate::date::Weekday::Wednesday => "Wed",
                crate::date::Weekday::Thursday => "Thu",
                crate::date::Weekday::Friday => "Fri",
                crate::date::Weekday::Saturday => "Sat",
                crate::date::Weekday::Sunday => "Sun",
            }
            .to_string()
        }
        "WW" => format!("{}", d.weekday()),
        "AMPM" => {
            if t.is_pm() {
                "PM"
            } else {
                "AM"
            }
            .to_string()
        }
        "ampm" => {
            if t.is_pm() {
                "pm"
            } else {
                "am"
            }
            .to_string()
        }
        _ => format!("{{{}}}", token),
    }
}

/// ISO 8601 datetime format: `YYYY-MM-DDTHH:mm:ss`
pub const ISO8601: &str = "{YYYY}-{MM}-{DD}T{HH}:{mm}:{ss}";
/// ISO 8601 date format: `YYYY-MM-DD`
pub const ISO8601_DATE: &str = "{YYYY}-{MM}-{DD}";
/// ISO 8601 time format: `HH:mm:ss`
pub const ISO8601_TIME: &str = "{HH}:{mm}:{ss}";
/// RFC 2822 format: `W, DD MM YYYY HH:mm:ss`
pub const RFC2822: &str = "{W}, {DD} {MM} {YYYY} {HH}:{mm}:{ss}";
/// Chinese date format: `YYYY年MM月DD日`
pub const CHINESE_DATE: &str = "{YYYY}年{MM}月{DD}日";
/// Chinese datetime format: `YYYY年MM月DD日 HH:mm:ss`
pub const CHINESE_DATETIME: &str = "{YYYY}年{MM}月{DD}日 {HH}:{mm}:{ss}";