deep-time 0.1.0-beta.7

High-precision, no-std, no-alloc date-time library, leap-seconds, time scales, relativistic time, and a powerful date & duration parser
Documentation
use crate::{Dt, MAX_YEAR, MIN_YEAR, Scale, TimeParts};

/// 6-digit legacy date: YYMMDD (e.g. "240315")
#[inline]
pub(crate) fn parse_yymmdd(input: &str) -> Option<Dt> {
    let parsed = TimeParts::from_str("%y%m%d", input, true, true, false).ok()?;
    parsed.to_dt().ok()
}

/// Parses year-month formats with flexible separators and optional sign:
/// "2024-03", "2024/3", "2024.03", "-2024-03", "-2024/3", "-2025.1", "+2024-05", etc.
pub(crate) fn parse_yyyy_mm(bytes: &[u8]) -> Option<Dt> {
    let len = bytes.len();

    // Parse optional leading sign for the year
    let (sign, mut pos) = match bytes.first() {
        Some(b'+') => (1i32, 1),
        Some(b'-') => (-1i32, 1),
        _ => (1i32, 0),
    };

    // Parse year digits (at least one required)
    let mut year = 0i32;
    let year_start = pos;
    while pos < len && bytes[pos].is_ascii_digit() {
        year = year * 10 + (bytes[pos] - b'0') as i32;
        pos += 1;
    }
    if pos == year_start || pos == len {
        return None;
    }

    // Must be followed by a valid separator
    if !matches!(bytes.get(pos), Some(b'-' | b'/' | b'.')) {
        return None;
    }
    pos += 1;

    // only valid cases (1 or 2 digits)
    let month = match len - pos {
        1 => {
            let b = bytes[pos];
            if !b.is_ascii_digit() {
                return None;
            }
            (b - b'0') as u32
        }
        2 => {
            let b1 = bytes[pos];
            let b2 = bytes[pos + 1];
            if !b1.is_ascii_digit() || !b2.is_ascii_digit() {
                return None;
            }
            10 * (b1 - b'0') as u32 + (b2 - b'0') as u32
        }
        _ => return None,
    };

    if month == 0 || month > 12 {
        return None;
    }

    year *= sign;
    if !(MIN_YEAR..=MAX_YEAR).contains(&year) {
        return None;
    }

    Some(Dt::from_ymd(
        year as i64,
        month as u8,
        1,
        0,
        0,
        0,
        0,
        Scale::UTC,
    ))
}

/// 6-digit year-month: "202403" or "-202403"
pub(crate) fn parse_yyyymm(s: &str) -> Option<Dt> {
    let (y_str, m_str) = if let Some(rest) = s.strip_prefix('-') {
        if rest.len() != 6 {
            return None;
        }
        (&rest[0..4], &rest[4..6])
    } else {
        if s.len() != 6 {
            return None;
        }
        (&s[0..4], &s[4..6])
    };

    if let (Ok(mut y), Ok(m)) = (y_str.parse::<i32>(), m_str.parse::<u32>()) {
        if s.starts_with('-') {
            y = -y;
        }
        if (1..=12).contains(&m) && (crate::MIN_YEAR..=crate::MAX_YEAR).contains(&y) {
            let parsed =
                TimeParts::from_str("%Y%m", s.trim_start_matches('-'), true, true, true).ok()?;
            return parsed.to_dt().ok();
        }
    }
    None
}