xmlgenerator 0.2.4

Generate XML instances from an XML Schema (XSD)
use crate::pattern::Pattern;
use crate::whitespace::WhiteSpace;
use rand::Rng;
use rand::rngs::StdRng;
use regex::Regex;

#[derive(PartialEq)]
pub(crate) struct Datetime {
    strf_time: String,
    pattern: Pattern,
}

fn generate_year(rng: &mut StdRng) -> i32 {
    let mut year = rng.random_range(1..10000);
    if year == 0 {
        return generate_year(rng);
    }
    if rng.random::<f32>() < 0.01 {
        year *= -1;
    }

    year
}

fn generate_day(rng: &mut StdRng, year: i32, month: i32) -> i32 {
    match month {
        9 | 4 | 6 | 11 => rng.random_range(1..=30),
        2 => {
            if year % 4 == 0 {
                rng.random_range(1..=29)
            } else {
                rng.random_range(1..=28)
            }
        }
        _ => rng.random_range(1..=31),
    }
}

impl Datetime {
    pub(crate) fn get_pattern(&self) -> &Pattern {
        &self.pattern
    }

    pub(crate) fn generate(&self, rng: &mut StdRng) -> Option<String> {
        if self.strf_time.is_empty() {
            return None;
        }

        let mut output = self.strf_time.clone();

        let year = generate_year(rng);
        if self.strf_time.contains("%Y") {
            let year_str = format!("{:04}", year);

            output = output.replace("%Y", &year_str);
        }

        let month = rng.random_range(1..=12);
        if self.strf_time.contains("%m") {
            let month_str = format!("{:02}", month);
            output = output.replace("%m", &month_str);
        }

        if self.strf_time.contains("%d") {
            let day = generate_day(rng, year, month);
            let day_str = format!("{:02}", day);
            output = output.replace("%d", &day_str);
        }

        if self.strf_time.contains("%H") {
            let hour = rng.random_range(0..24);
            let hour_str = format!("{:02}", hour);
            output = output.replace("%H", &hour_str);
        }

        if self.strf_time.contains("%M") {
            let minute = rng.random_range(0..60);
            let minute_str = format!("{:02}", minute);
            output = output.replace("%M", &minute_str);
        }

        if self.strf_time.contains("%S") {
            let second = rng.random_range(0..60);
            let second_str = format!("{:02}", second);
            output = output.replace("%S", &second_str);
        }

        if output.contains("%") {
            panic!("Raw time string not replaced");
        }

        Some(output)
    }

    pub(crate) fn matches(&self, string: &str) -> bool {
        let full_pattern = format!("^{}$", self.pattern.get_pattern(true));
        let regex = Regex::new(&full_pattern).unwrap();
        regex.is_match(string)
    }
}

impl From<&str> for Datetime {
    fn from(s: &str) -> Self {
        const YEAR: &str = r"(-?(?:[0-9]{3}[1-9])|(?:[0-9]{2}[1-9][0-9])|(?:[0-9][1-9][0-9]{2})|(?:[1-9][0-9]{3}))";
        const MONTH: &str = r"(?:0[1-9]|1[0-2])";
        const DAY: &str = r"(?:0[1-9]|1[0-9]|2[0-9]|3[0-1])";

        const HOUR: &str = r"(?:0[1-9]|1[0-9]|2[0-3])";
        const MINUTE: &str = r"[0-5][0-9]";
        const SECOND: &str = r"[0-5][0-9]";

        let output = s
            .replace("%Y", YEAR)
            .replace("%m", MONTH)
            .replace("%d", DAY)
            .replace("%H", HOUR)
            .replace("%M", MINUTE)
            .replace("%S", SECOND);

        if output.contains("%") {
            panic!("Raw time string not replaced");
        }

        Datetime {
            strf_time: s.to_string(),
            pattern: Pattern::from_string(&output, &WhiteSpace::Collapse).unwrap(),
        }
    }
}

impl std::fmt::Display for Datetime {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", self.pattern)
    }
}