Skip to main content

npdatetime/lookup/
mod.rs

1// Fast lookup table approach
2// Lookup module
3
4use crate::core::date::BS_EPOCH_YEAR;
5use crate::core::error::{NpdatetimeError, Result};
6
7// Include data generated by build.rs
8include!(concat!(env!("OUT_DIR"), "/calendar_data.rs"));
9
10/// Map the generated flat data to the expected format
11fn get_bs_month_data() -> Vec<[u8; 12]> {
12    let mut result = Vec::new();
13    let mut current_year = -1;
14    let mut year_data = [0u8; 12];
15
16    for &(year, month, days) in BS_CALENDAR_DATA {
17        if year != current_year {
18            if current_year != -1 {
19                result.push(year_data);
20            }
21            current_year = year;
22            year_data = [0u8; 12];
23        }
24        if (1..=12).contains(&month) {
25            year_data[month as usize - 1] = days;
26        }
27    }
28    result.push(year_data);
29    result
30}
31
32lazy_static::lazy_static! {
33    static ref BS_MONTH_DATA: Vec<[u8; 12]> = get_bs_month_data();
34}
35
36/// Returns the number of days in a given BS month using the lookup table
37pub fn get_days_in_month(year: i32, month: u8) -> Result<u8> {
38    let index = (year - BS_EPOCH_YEAR) as usize;
39    if index >= BS_MONTH_DATA.len() {
40        return Err(NpdatetimeError::OutOfRange(format!(
41            "Year {} is out of supported range",
42            year
43        )));
44    }
45
46    Ok(BS_MONTH_DATA[index][(month - 1) as usize])
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn test_csv_data_loaded() {
55        // Verify data is loaded (should have 126 years from 1975-2100)
56        assert!(
57            !BS_MONTH_DATA.is_empty(),
58            "BS_MONTH_DATA should not be empty"
59        );
60        assert_eq!(BS_MONTH_DATA.len(), 126, "Expected 126 years of data");
61    }
62
63    #[test]
64    fn test_first_year_data() {
65        // 1975: 31,31,32,32,31,30,30,29,30,29,30,30
66        let days = get_days_in_month(1975, 1).unwrap();
67        assert_eq!(days, 31, "1975 Baisakh should have 31 days");
68
69        let days = get_days_in_month(1975, 5).unwrap();
70        assert_eq!(days, 31, "1975 Bhadra should have 31 days");
71
72        let days = get_days_in_month(1975, 12).unwrap();
73        assert_eq!(days, 30, "1975 Chaitra should have 30 days");
74    }
75
76    #[test]
77    fn test_known_year_2077() {
78        // 2077: 31,32,31,32,31,30,30,30,29,30,29,31
79        assert_eq!(get_days_in_month(2077, 1).unwrap(), 31);
80        assert_eq!(get_days_in_month(2077, 2).unwrap(), 32);
81        assert_eq!(get_days_in_month(2077, 5).unwrap(), 31);
82        assert_eq!(get_days_in_month(2077, 12).unwrap(), 31);
83    }
84
85    #[test]
86    fn test_last_year_data() {
87        // 2100: 31,32,31,32,30,31,30,29,30,29,30,30
88        let days = get_days_in_month(2100, 1).unwrap();
89        assert_eq!(days, 31, "2100 Baisakh should have 31 days");
90
91        let days = get_days_in_month(2100, 12).unwrap();
92        assert_eq!(days, 30, "2100 Chaitra should have 30 days");
93    }
94
95    #[test]
96    fn test_month_range_validation() {
97        // Valid months (1-12)
98        for month in 1..=12 {
99            assert!(get_days_in_month(2077, month).is_ok());
100        }
101    }
102
103    #[test]
104    fn test_out_of_range_year_before() {
105        let result = get_days_in_month(1974, 1);
106        assert!(result.is_err(), "Year 1974 should be out of range");
107    }
108
109    #[test]
110    fn test_out_of_range_year_after() {
111        let result = get_days_in_month(2101, 1);
112        assert!(result.is_err(), "Year 2101 should be out of range");
113    }
114
115    #[test]
116    fn test_all_years_all_months() {
117        // Comprehensive test: verify all years and months have valid day counts
118        for year in 1975..=2100 {
119            for month in 1..=12 {
120                let days = get_days_in_month(year, month).unwrap();
121                assert!(
122                    (29..=32).contains(&days),
123                    "Year {} month {} has invalid day count: {}",
124                    year,
125                    month,
126                    days
127                );
128            }
129        }
130    }
131
132    #[test]
133    fn test_year_totals_within_range() {
134        // Each BS year should have 354-385 total days
135        for year in 1975..=2100 {
136            let total: u32 = (1..=12)
137                .map(|m| get_days_in_month(year, m).unwrap() as u32)
138                .sum();
139            assert!(
140                (354..=385).contains(&total),
141                "Year {} has invalid total days: {}",
142                year,
143                total
144            );
145        }
146    }
147}