sac13 0.1.1

The reference implementation for the SAC13 calendar system.
Documentation
use crate::iterhelp::ByteSliceIter;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ParsedComponent {
    /// Flag if component started with a letter
    pub letter: bool,
    /// Character count (without negitave sign if present)
    pub char_cnt: u8,
    pub value: i16,
}

impl ParsedComponent {
    pub fn is_year_comp(&self) -> bool {
        self.is_gregorian_year() || self.is_sac13_year()
    }

    pub fn is_gregorian_year(&self) -> bool {
        !self.letter && (self.value <= 0 || self.char_cnt >= 3)
    }

    pub fn is_sac13_year(&self) -> bool {
        self.letter && self.char_cnt == 4
    }

    pub fn parse(i: &mut ByteSliceIter) -> Option<Self> {
        let mut num_comp = ParsedComponent {
            letter: false,
            value: 0,
            char_cnt: 0,
        };

        let mut peeked = i.peek()?;

        let invert = peeked == b'-';

        if invert {
            // consume peeked negative sign, and peek next
            _ = i.next();
            peeked = i.peek()?;
        }

        if peeked.is_ascii_uppercase() {
            if invert {
                // negative SAC13 years are not allowed
                return None;
            }

            // consume and process prefix letter
            _ = i.next();
            num_comp.value = (peeked - b'A') as i16;
            num_comp.letter = true;
            num_comp.char_cnt += 1;
        }

        // After this point only ASCII digits are allowed
        loop {
            let Some(peeked) = i.peek() else {
                // if we reached the end -> break and return the result
                break;
            };

            if !peeked.is_ascii_digit() {
                // if the peeked byte is not a digit, also break and return
                break;
            }

            _ = i.next();
            let digit_value = (peeked - b'0') as i16;

            num_comp.char_cnt += 1;
            num_comp.value = num_comp.value.checked_mul(10)?;
            num_comp.value = num_comp.value.checked_add(digit_value)?;
        }

        if invert {
            num_comp.value *= -1;
        }

        Some(num_comp)
    }
}