spacecell 0.1.0

Datetime library with ISO8601/RFC3339 parsing, calendar arithmetic, and business day calculations
Documentation
//! # **Config Module** - *Calendar configuration*
//!
//! Configurable calendar settings for fiscal years, week starts, and holidays.

use std::collections::HashMap;

/// Calendar configuration for fiscal years, week starts, and holidays
#[derive(Debug, Clone)]
pub struct CalendarConfig {
    /// First day of week (0=Monday, 6=Sunday)
    pub week_start: u32,

    /// Fiscal year start month (1-12)
    pub fiscal_year_start: u32,

    /// Fiscal quarter start month (1-12)
    pub fiscal_quarter_start: u32,

    /// Public holidays by year
    /// Key: year, Value: Vec of (month, day) tuples
    pub holidays: HashMap<i32, Vec<(u32, u32)>>,
}

impl Default for CalendarConfig {
    fn default() -> Self {
        Self {
            week_start: 0,                // Monday
            fiscal_year_start: 1,         // January
            fiscal_quarter_start: 1,      // January
            holidays: HashMap::new(),
        }
    }
}

impl CalendarConfig {
    /// Create a new default configuration
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the first day of week (0=Monday, 6=Sunday)
    pub fn with_week_start(mut self, day: u32) -> Self {
        self.week_start = day % 7;
        self
    }

    /// Set the fiscal year start month (1-12)
    pub fn with_fiscal_year_start(mut self, month: u32) -> Self {
        self.fiscal_year_start = month.clamp(1, 12);
        self
    }

    /// Set the fiscal quarter start month (1-12)
    pub fn with_fiscal_quarter_start(mut self, month: u32) -> Self {
        self.fiscal_quarter_start = month.clamp(1, 12);
        self
    }

    /// Add a single holiday for a specific year
    pub fn add_holiday(mut self, year: i32, month: u32, day: u32) -> Self {
        self.holidays
            .entry(year)
            .or_insert_with(Vec::new)
            .push((month, day));
        self
    }

    /// Add multiple holidays for a specific year
    pub fn add_holidays(mut self, year: i32, holidays: &[(u32, u32)]) -> Self {
        self.holidays
            .entry(year)
            .or_insert_with(Vec::new)
            .extend_from_slice(holidays);
        self
    }

    /// Check if a date is a configured holiday
    pub fn is_holiday(&self, year: i32, month: u32, day: u32) -> bool {
        self.holidays
            .get(&year)
            .map(|h| h.contains(&(month, day)))
            .unwrap_or(false)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_default_config() {
        let config = CalendarConfig::default();
        assert_eq!(config.week_start, 0); // Monday
        assert_eq!(config.fiscal_year_start, 1); // January
        assert_eq!(config.fiscal_quarter_start, 1);
    }

    #[test]
    fn test_with_week_start() {
        let config = CalendarConfig::new().with_week_start(6); // Sunday
        assert_eq!(config.week_start, 6);
    }

    #[test]
    fn test_with_fiscal_year() {
        let config = CalendarConfig::new().with_fiscal_year_start(7); // July
        assert_eq!(config.fiscal_year_start, 7);
    }

    #[test]
    fn test_add_holiday() {
        let config = CalendarConfig::new().add_holiday(2024, 12, 25); // Christmas
        assert!(config.is_holiday(2024, 12, 25));
        assert!(!config.is_holiday(2024, 12, 26));
    }

    #[test]
    fn test_add_holidays() {
        let holidays = vec![(1, 1), (7, 4), (12, 25)]; // New Year, July 4th, Christmas
        let config = CalendarConfig::new().add_holidays(2024, &holidays);

        assert!(config.is_holiday(2024, 1, 1));
        assert!(config.is_holiday(2024, 7, 4));
        assert!(config.is_holiday(2024, 12, 25));
        assert!(!config.is_holiday(2024, 6, 15));
    }

    #[test]
    fn test_is_holiday_different_year() {
        let config = CalendarConfig::new().add_holiday(2024, 12, 25);
        assert!(config.is_holiday(2024, 12, 25));
        assert!(!config.is_holiday(2025, 12, 25)); // Different year
    }
}