npdatetime 0.2.4

Astronomical calculator for Bikram Sambat calendar based on solar and lunar positions. High-performance Nepali (Bikram Sambat) datetime library with multi-language bindings
Documentation
// Fast lookup table approach
// Lookup module

use crate::core::date::BS_EPOCH_YEAR;
use crate::core::error::{NpdatetimeError, Result};

// Include data generated by build.rs
include!(concat!(env!("OUT_DIR"), "/calendar_data.rs"));

/// Map the generated flat data to the expected format
fn get_bs_month_data() -> Vec<[u8; 12]> {
    let mut result = Vec::new();
    let mut current_year = -1;
    let mut year_data = [0u8; 12];

    for &(year, month, days) in BS_CALENDAR_DATA {
        if year != current_year {
            if current_year != -1 {
                result.push(year_data);
            }
            current_year = year;
            year_data = [0u8; 12];
        }
        if (1..=12).contains(&month) {
            year_data[month as usize - 1] = days;
        }
    }
    result.push(year_data);
    result
}

lazy_static::lazy_static! {
    static ref BS_MONTH_DATA: Vec<[u8; 12]> = get_bs_month_data();
}

/// Returns the number of days in a given BS month using the lookup table
pub fn get_days_in_month(year: i32, month: u8) -> Result<u8> {
    let index = (year - BS_EPOCH_YEAR) as usize;
    if index >= BS_MONTH_DATA.len() {
        return Err(NpdatetimeError::OutOfRange(format!(
            "Year {} is out of supported range",
            year
        )));
    }

    Ok(BS_MONTH_DATA[index][(month - 1) as usize])
}

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

    #[test]
    fn test_csv_data_loaded() {
        // Verify data is loaded (should have 126 years from 1975-2100)
        assert!(
            !BS_MONTH_DATA.is_empty(),
            "BS_MONTH_DATA should not be empty"
        );
        assert_eq!(BS_MONTH_DATA.len(), 126, "Expected 126 years of data");
    }

    #[test]
    fn test_first_year_data() {
        // 1975: 31,31,32,32,31,30,30,29,30,29,30,30
        let days = get_days_in_month(1975, 1).unwrap();
        assert_eq!(days, 31, "1975 Baisakh should have 31 days");

        let days = get_days_in_month(1975, 5).unwrap();
        assert_eq!(days, 31, "1975 Bhadra should have 31 days");

        let days = get_days_in_month(1975, 12).unwrap();
        assert_eq!(days, 30, "1975 Chaitra should have 30 days");
    }

    #[test]
    fn test_known_year_2077() {
        // 2077: 31,32,31,32,31,30,30,30,29,30,29,31
        assert_eq!(get_days_in_month(2077, 1).unwrap(), 31);
        assert_eq!(get_days_in_month(2077, 2).unwrap(), 32);
        assert_eq!(get_days_in_month(2077, 5).unwrap(), 31);
        assert_eq!(get_days_in_month(2077, 12).unwrap(), 31);
    }

    #[test]
    fn test_last_year_data() {
        // 2100: 31,32,31,32,30,31,30,29,30,29,30,30
        let days = get_days_in_month(2100, 1).unwrap();
        assert_eq!(days, 31, "2100 Baisakh should have 31 days");

        let days = get_days_in_month(2100, 12).unwrap();
        assert_eq!(days, 30, "2100 Chaitra should have 30 days");
    }

    #[test]
    fn test_month_range_validation() {
        // Valid months (1-12)
        for month in 1..=12 {
            assert!(get_days_in_month(2077, month).is_ok());
        }
    }

    #[test]
    fn test_out_of_range_year_before() {
        let result = get_days_in_month(1974, 1);
        assert!(result.is_err(), "Year 1974 should be out of range");
    }

    #[test]
    fn test_out_of_range_year_after() {
        let result = get_days_in_month(2101, 1);
        assert!(result.is_err(), "Year 2101 should be out of range");
    }

    #[test]
    fn test_all_years_all_months() {
        // Comprehensive test: verify all years and months have valid day counts
        for year in 1975..=2100 {
            for month in 1..=12 {
                let days = get_days_in_month(year, month).unwrap();
                assert!(
                    (29..=32).contains(&days),
                    "Year {} month {} has invalid day count: {}",
                    year,
                    month,
                    days
                );
            }
        }
    }

    #[test]
    fn test_year_totals_within_range() {
        // Each BS year should have 354-385 total days
        for year in 1975..=2100 {
            let total: u32 = (1..=12)
                .map(|m| get_days_in_month(year, m).unwrap() as u32)
                .sum();
            assert!(
                (354..=385).contains(&total),
                "Year {} has invalid total days: {}",
                year,
                total
            );
        }
    }
}