1use chrono::{Datelike, Local, NaiveDate, Weekday};
12
13use crate::persian::MONTH_DAYS;
14
15const SHAHANSHAHI_OFFSET: i32 = 1180;
17
18const fn to_jalali_year(sy: i32) -> i32 {
20 sy - SHAHANSHAHI_OFFSET
21}
22
23const fn to_shahanshahi_year(jy: i32) -> i32 {
25 jy + SHAHANSHAHI_OFFSET
26}
27
28#[must_use]
31pub const fn gregorian_to_shahanshahi(gy: i32, gm: u32, gd: u32) -> (i32, u32, u32) {
32 let (jy, jm, jd) = gregorian_to_jalali(gy, gm, gd);
33 (to_shahanshahi_year(jy), jm, jd)
34}
35
36#[must_use]
39pub fn shahanshahi_to_gregorian(sy: i32, sm: u32, sd: u32) -> (i32, u32, u32) {
40 jalali_to_gregorian(to_jalali_year(sy), sm, sd)
41}
42
43#[must_use]
46pub const fn is_leap(sy: i32) -> bool {
47 let jy = to_jalali_year(sy);
48 let r = jy.rem_euclid(33);
49 matches!(r, 1 | 5 | 9 | 13 | 17 | 22 | 26 | 30)
50}
51
52#[must_use]
54pub const fn days_in_month(year: i32, month: u32) -> u32 {
55 if month == 12 && is_leap(year) {
56 30
57 } else {
58 MONTH_DAYS[month as usize - 1]
59 }
60}
61
62#[must_use]
64pub fn today() -> (i32, u32, u32) {
65 let now = Local::now();
66 gregorian_to_shahanshahi(now.year(), now.month(), now.day())
67}
68
69#[must_use]
78pub fn weekday_of_first(year: i32, month: u32) -> u32 {
79 let (gy, gm, gd) = shahanshahi_to_gregorian(year, month, 1);
80 let date = NaiveDate::from_ymd_opt(gy, gm, gd).expect("invalid date");
81 match date.weekday() {
82 Weekday::Sat => 0,
83 Weekday::Sun => 1,
84 Weekday::Mon => 2,
85 Weekday::Tue => 3,
86 Weekday::Wed => 4,
87 Weekday::Thu => 5,
88 Weekday::Fri => 6,
89 }
90}
91
92const fn gregorian_to_jalali(gy: i32, gm: u32, gd: u32) -> (i32, u32, u32) {
95 let g_d_m = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
96
97 let gy2 = if gm > 2 { gy + 1 } else { gy };
98 let mut days = 355_666 + (365 * gy) + ((gy2 + 3) / 4) - ((gy2 + 99) / 100)
99 + ((gy2 + 399) / 400)
100 + gd as i32
101 + g_d_m[gm as usize - 1];
102
103 let mut jy = -1595 + (33 * (days / 12_053));
104 days %= 12_053;
105
106 jy += 4 * (days / 1461);
107 days %= 1461;
108
109 if days > 365 {
110 jy += (days - 1) / 365;
111 days = (days - 1) % 365;
112 }
113
114 let (jm, jd) = if days < 186 {
115 let jm = 1 + (days / 31);
116 let jd = 1 + (days % 31);
117 (jm, jd)
118 } else {
119 let days = days - 186;
120 let jm = 7 + (days / 30);
121 let jd = 1 + (days % 30);
122 (jm, jd)
123 };
124
125 (jy, jm as u32, jd as u32)
126}
127
128fn jalali_to_gregorian(jy: i32, jm: u32, jd: u32) -> (i32, u32, u32) {
129 let jy = jy - 979;
130 let jm = jm as i32 - 1;
131 let jd = jd as i32 - 1;
132
133 let mut j_day_no = 365 * jy + (jy / 33) * 8 + (jy % 33 + 3) / 4;
134 for i in 0..jm {
135 j_day_no += if i < 6 { 31 } else { 30 };
136 }
137 j_day_no += jd;
138
139 let mut g_day_no = j_day_no + 79;
140
141 let mut gy = 1600 + 400 * (g_day_no / 146_097);
142 g_day_no %= 146_097;
143
144 let mut leap = true;
145 if g_day_no >= 36_525 {
146 g_day_no -= 1;
147 gy += 100 * (g_day_no / 36_524);
148 g_day_no %= 36_524;
149
150 if g_day_no >= 365 {
151 g_day_no += 1;
152 } else {
153 leap = false;
154 }
155 }
156
157 gy += 4 * (g_day_no / 1461);
158 g_day_no %= 1461;
159
160 if g_day_no >= 366 {
161 leap = false;
162 g_day_no -= 1;
163 gy += g_day_no / 365;
164 g_day_no %= 365;
165 }
166
167 let g_d_m: [i32; 12] = [
168 31,
169 if leap { 29 } else { 28 },
170 31,
171 30,
172 31,
173 30,
174 31,
175 31,
176 30,
177 31,
178 30,
179 31,
180 ];
181
182 let mut gm = 0;
183 for (i, &days) in g_d_m.iter().enumerate() {
184 if g_day_no < days {
185 break;
186 }
187 g_day_no -= days;
188 gm = i + 1;
189 }
190
191 (gy, gm as u32 + 1, g_day_no as u32 + 1)
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn test_gregorian_to_shahanshahi() {
200 assert_eq!(gregorian_to_shahanshahi(2025, 3, 20), (2583, 12, 30));
202 assert_eq!(gregorian_to_shahanshahi(2025, 3, 21), (2584, 1, 1));
204 assert_eq!(gregorian_to_shahanshahi(2024, 3, 20), (2583, 1, 1));
206 assert_eq!(gregorian_to_shahanshahi(2026, 3, 15), (2584, 12, 24));
208 }
209
210 #[test]
211 fn test_shahanshahi_to_gregorian() {
212 assert_eq!(shahanshahi_to_gregorian(2584, 1, 1), (2025, 3, 21));
213 assert_eq!(shahanshahi_to_gregorian(2583, 1, 1), (2024, 3, 20));
214 assert_eq!(shahanshahi_to_gregorian(2584, 12, 24), (2026, 3, 15));
215 }
216
217 #[test]
218 fn test_roundtrip() {
219 for (sy, sm, sd) in [(2584, 1, 1), (2583, 6, 15), (2580, 12, 29), (2579, 12, 30)] {
220 let (gy, gm, gd) = shahanshahi_to_gregorian(sy, sm, sd);
221 assert_eq!(gregorian_to_shahanshahi(gy, gm, gd), (sy, sm, sd));
222 }
223 }
224
225 #[test]
226 fn test_is_leap() {
227 assert!(is_leap(2579));
229 assert!(is_leap(2583));
231 assert!(!is_leap(2584));
233 assert!(!is_leap(2581));
235 }
236
237 #[test]
238 fn test_days_in_month() {
239 assert_eq!(days_in_month(2584, 1), 31);
240 assert_eq!(days_in_month(2584, 7), 30);
241 assert_eq!(days_in_month(2584, 12), 29); assert_eq!(days_in_month(2583, 12), 30); }
244
245 #[test]
246 fn test_weekday_of_first() {
247 assert_eq!(weekday_of_first(2584, 1), 6);
249 }
250}