pcal/
calendar.rs

1//! Month grid computation for the Shahanshahi calendar.
2//!
3//! Builds a 6×7 grid of day numbers for any given month, used by both
4//! the colored display and plain-text formatter.
5
6use crate::shahanshahi;
7
8/// A 6×7 grid representing a single Shahanshahi calendar month.
9///
10/// Rows are weeks, columns are weekdays starting from Saturday (column 0)
11/// through Friday (column 6), following the Iranian week convention.
12/// Each cell is `None` for empty positions or `Some(day)` for a valid day.
13pub struct MonthGrid {
14    /// Shahanshahi year (e.g. 2584).
15    pub year: i32,
16    /// Persian month (1–12).
17    pub month: u32,
18    /// Total number of days in this month.
19    pub days: u32,
20    /// Weekday index of the 1st day (0=Saturday, 6=Friday).
21    pub first_weekday: u32,
22    /// The grid: 6 rows × 7 columns. `None` = empty cell, `Some(day)` = day number.
23    pub grid: [[Option<u32>; 7]; 6],
24}
25
26impl MonthGrid {
27    /// Build a new month grid for the given Shahanshahi year and month.
28    #[must_use]
29    pub fn new(year: i32, month: u32) -> Self {
30        let days = shahanshahi::days_in_month(year, month);
31        let first_weekday = shahanshahi::weekday_of_first(year, month);
32
33        let mut grid = [[None; 7]; 6];
34        let mut day = 1u32;
35        let mut row = 0;
36        let mut col = first_weekday as usize;
37
38        while day <= days {
39            grid[row][col] = Some(day);
40            day += 1;
41            col += 1;
42            if col >= 7 {
43                col = 0;
44                row += 1;
45            }
46        }
47
48        Self {
49            year,
50            month,
51            days,
52            first_weekday,
53            grid,
54        }
55    }
56
57    /// Number of rows actually used in the grid (between 4 and 6).
58    #[must_use]
59    pub fn row_count(&self) -> usize {
60        for r in (0..6).rev() {
61            if self.grid[r].iter().any(|c| c.is_some()) {
62                return r + 1;
63            }
64        }
65        0
66    }
67
68    /// Get day-of-year (1–366) for a given day in this month.
69    #[must_use]
70    pub fn day_of_year(&self, day: u32) -> u32 {
71        let mut doy = 0;
72        for m in 1..self.month {
73            doy += shahanshahi::days_in_month(self.year, m);
74        }
75        doy + day
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_month_grid_farvardin_2584() {
85        let grid = MonthGrid::new(2584, 1);
86        assert_eq!(grid.days, 31);
87        // Farvardin 2584 starts on Friday (col 6)
88        assert_eq!(grid.first_weekday, 6);
89        assert_eq!(grid.grid[0][6], Some(1));
90        assert_eq!(grid.grid[1][0], Some(2)); // Saturday = day 2
91    }
92
93    #[test]
94    fn test_month_grid_row_count() {
95        let grid = MonthGrid::new(2584, 1);
96        assert!(grid.row_count() >= 5);
97    }
98
99    #[test]
100    fn test_day_of_year() {
101        let grid = MonthGrid::new(2584, 1);
102        assert_eq!(grid.day_of_year(1), 1);
103        assert_eq!(grid.day_of_year(31), 31);
104
105        let grid2 = MonthGrid::new(2584, 2);
106        assert_eq!(grid2.day_of_year(1), 32);
107    }
108}