rstime 0.1.0

A zero-dependency Rust time library providing date, time, datetime types with formatting, parsing, Unix timestamps, and clock functionality.
Documentation
//! Parse module
//!
//! Provides functions for parsing date/time strings using format patterns
//! and ISO 8601 format detection.

use crate::date::Date;
use crate::datetime::DateTime;
use crate::error::{RstimeError, RstimeResult};
use crate::time::Time;

/// Parse a datetime string using a format pattern
///
/// # Parameters
///
/// * `s` - The input string to parse
/// * `fmt` - Format pattern using `{TOKEN}` syntax
///
/// # Returns
///
/// A [`DateTime`] on success, or an error message string.
///
/// # Examples
///
/// ```rust
/// use rstime::parse_datetime;
///
/// let dt = parse_datetime(
///     "10/05/2026 14:05",
///     "{DD}/{MM}/{YYYY} {HH}:{mm}"
/// ).unwrap();
/// assert_eq!(dt.date.year, 2026);
/// assert_eq!(dt.date.month, 5);
/// assert_eq!(dt.date.day, 10);
/// ```
pub fn parse_datetime(s: &str, fmt: &str) -> RstimeResult<DateTime> {
    let tokens = tokenize_format(fmt);
    let mut year = 0i32;
    let mut month = 1u8;
    let mut day = 1u8;
    let mut hour = 0u8;
    let mut minute = 0u8;
    let mut second = 0u8;
    let mut millisecond = 0u16;
    let mut pos = 0usize;
    let chars: Vec<char> = s.chars().collect();

    for token in &tokens {
        match token {
            Token::Literal(lit) => {
                let lit_chars: Vec<char> = lit.chars().collect();
                if pos + lit_chars.len() > chars.len() {
                    return Err(RstimeError::new(format!(
                        "期望文本 '{}' 但输入已结束",
                        lit
                    )));
                }
                let actual: String = chars[pos..pos + lit_chars.len()].iter().collect();
                if actual != *lit {
                    return Err(RstimeError::new(format!(
                        "位置 {} 期望 '{}',实际得到 '{}'",
                        pos, lit, actual
                    )));
                }
                pos += lit_chars.len();
            }
            Token::Specifier(spec) => {
                let value = read_number(&chars, &mut pos, spec.len);
                match spec.kind {
                    SpecKind::Year4 => year = value as i32,
                    SpecKind::Year2 => year = 2000 + value as i32,
                    SpecKind::Month => month = value as u8,
                    SpecKind::Day => day = value as u8,
                    SpecKind::Hour24 => hour = value as u8,
                    SpecKind::Hour12 => hour = value as u8,
                    SpecKind::Minute => minute = value as u8,
                    SpecKind::Second => second = value as u8,
                    SpecKind::Millisecond => millisecond = value as u16,
                }
            }
        }
    }

    let date = Date::new(year, month, day);
    let time = Time::new(hour, minute, second, millisecond);
    Ok(DateTime::new(date, time))
}

/// Parse a date string using a format pattern
///
/// # Examples
///
/// ```rust
/// use rstime::parse_date;
///
/// let d = parse_date("2026-05-10", "{YYYY}-{MM}-{DD}").unwrap();
/// assert_eq!(d.year, 2026);
/// assert_eq!(d.month, 5);
/// assert_eq!(d.day, 10);
/// ```
pub fn parse_date(s: &str, fmt: &str) -> RstimeResult<Date> {
    let dt = parse_datetime(s, fmt)?;
    Ok(dt.date)
}

/// Parse a time string using a format pattern
///
/// # Examples
///
/// ```rust
/// use rstime::parse_time;
///
/// let t = parse_time("14:05:09", "{HH}:{mm}:{ss}").unwrap();
/// assert_eq!(t.hour, 14);
/// assert_eq!(t.minute, 5);
/// assert_eq!(t.second, 9);
/// ```
pub fn parse_time(s: &str, fmt: &str) -> RstimeResult<Time> {
    let dt = parse_datetime(&format!("2000-01-01 {}", s), &format!("{{YYYY}}-{{MM}}-{{DD}} {}", fmt))?;
    Ok(dt.time)
}

/// Parse an ISO 8601 formatted datetime string
///
/// Supports the following formats:
/// - `2026-05-10T14:05:09.037` (with milliseconds)
/// - `2026-05-10T14:05:09` (seconds precision)
/// - `2026-05-10 14:05:09` (space separator)
/// - `2026-05-10` (date only, time set to midnight)
///
/// # Examples
///
/// ```rust
/// use rstime::{parse_iso8601, Date, Time};
///
/// let dt = parse_iso8601("2026-05-10T14:05:09").unwrap();
/// assert_eq!(dt.date, Date::new(2026, 5, 10));
/// assert_eq!(dt.time, Time::from_hms(14, 5, 9));
///
/// let dt2 = parse_iso8601("2026-05-10").unwrap();
/// assert_eq!(dt2.time, Time::MIDNIGHT);
/// ```
pub fn parse_iso8601(s: &str) -> RstimeResult<DateTime> {
    let s = s.trim();

    if s.len() >= 23
        && s.as_bytes()[4] == b'-'
        && s.as_bytes()[7] == b'-'
        && (s.as_bytes()[10] == b'T' || s.as_bytes()[10] == b' ')
        && s.as_bytes()[13] == b':'
        && s.as_bytes()[16] == b':'
        && s.as_bytes()[19] == b'.'
    {
        return parse_datetime(s, "{YYYY}-{MM}-{DD}T{HH}:{mm}:{ss}.{SSS}");
    }

    if s.len() >= 19
        && s.as_bytes()[4] == b'-'
        && s.as_bytes()[7] == b'-'
        && (s.as_bytes()[10] == b'T' || s.as_bytes()[10] == b' ')
    {
        return parse_datetime(s, "{YYYY}-{MM}-{DD}T{HH}:{mm}:{ss}");
    }

    if s.len() >= 10 && s.as_bytes()[4] == b'-' && s.as_bytes()[7] == b'-' {
        let dt = parse_datetime(s, "{YYYY}-{MM}-{DD}")?;
        return Ok(DateTime::new(dt.date, Time::MIDNIGHT));
    }

    Err(RstimeError::new(format!("无法识别的 ISO 8601 格式: '{}'", s)))
}

enum SpecKind {
    Year4,
    Year2,
    Month,
    Day,
    Hour24,
    Hour12,
    Minute,
    Second,
    Millisecond,
}

struct FormatSpec {
    kind: SpecKind,
    len: usize,
}

enum Token {
    Literal(String),
    Specifier(FormatSpec),
}

fn tokenize_format(fmt: &str) -> Vec<Token> {
    let mut tokens = Vec::new();
    let chars: Vec<char> = fmt.chars().collect();
    let mut i = 0;

    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();
                if let Some(spec) = classify_token(&token) {
                    tokens.push(Token::Specifier(spec));
                    i = end + 1;
                    continue;
                }
            }
        }
        let start = i;
        while i < chars.len() && (chars[i] != '{' || i == start) {
            if chars[i] == '{' {
                let mut j = i + 1;
                while j < chars.len() && chars[j] != '}' {
                    j += 1;
                }
                if j < chars.len() {
                    let t: String = chars[i + 1..j].iter().collect();
                    if classify_token(&t).is_some() {
                        break;
                    }
                }
            }
            i += 1;
        }
        if i > start {
            tokens.push(Token::Literal(chars[start..i].iter().collect()));
        }
    }

    tokens
}

fn classify_token(token: &str) -> Option<FormatSpec> {
    match token {
        "YYYY" => Some(FormatSpec {
            kind: SpecKind::Year4,
            len: 4,
        }),
        "YY" => Some(FormatSpec {
            kind: SpecKind::Year2,
            len: 2,
        }),
        "MM" => Some(FormatSpec {
            kind: SpecKind::Month,
            len: 2,
        }),
        "M" => Some(FormatSpec {
            kind: SpecKind::Month,
            len: 1,
        }),
        "DD" => Some(FormatSpec {
            kind: SpecKind::Day,
            len: 2,
        }),
        "D" => Some(FormatSpec {
            kind: SpecKind::Day,
            len: 1,
        }),
        "HH" => Some(FormatSpec {
            kind: SpecKind::Hour24,
            len: 2,
        }),
        "H" => Some(FormatSpec {
            kind: SpecKind::Hour24,
            len: 1,
        }),
        "hh" => Some(FormatSpec {
            kind: SpecKind::Hour12,
            len: 2,
        }),
        "h" => Some(FormatSpec {
            kind: SpecKind::Hour12,
            len: 1,
        }),
        "mm" => Some(FormatSpec {
            kind: SpecKind::Minute,
            len: 2,
        }),
        "m" => Some(FormatSpec {
            kind: SpecKind::Minute,
            len: 1,
        }),
        "ss" => Some(FormatSpec {
            kind: SpecKind::Second,
            len: 2,
        }),
        "s" => Some(FormatSpec {
            kind: SpecKind::Second,
            len: 1,
        }),
        "SSS" => Some(FormatSpec {
            kind: SpecKind::Millisecond,
            len: 3,
        }),
        _ => None,
    }
}

fn read_number(chars: &[char], pos: &mut usize, len: usize) -> u32 {
    let end = (*pos + len).min(chars.len());
    let num_str: String = chars[*pos..end].iter().collect();
    *pos = end;
    num_str.parse::<u32>().unwrap_or(0)
}