calendrical_calculations/
persian.rs1use crate::helpers::{i64_to_i32, I32CastError, IntegerRoundings};
11use crate::rata_die::RataDie;
12
13const FIXED_PERSIAN_EPOCH: RataDie = crate::julian::fixed_from_julian(622, 3, 19);
16
17const NON_LEAP_CORRECTION: [i32; 78] = [
24 1502, 1601, 1634, 1667, 1700, 1733, 1766, 1799, 1832, 1865, 1898, 1931, 1964, 1997, 2030, 2059,
25 2063, 2096, 2129, 2158, 2162, 2191, 2195, 2224, 2228, 2257, 2261, 2290, 2294, 2323, 2327, 2356,
26 2360, 2389, 2393, 2422, 2426, 2455, 2459, 2488, 2492, 2521, 2525, 2554, 2558, 2587, 2591, 2620,
27 2624, 2653, 2657, 2686, 2690, 2719, 2723, 2748, 2752, 2756, 2781, 2785, 2789, 2818, 2822, 2847,
28 2851, 2855, 2880, 2884, 2888, 2913, 2917, 2921, 2946, 2950, 2954, 2979, 2983, 2987,
29];
30
31const MIN_NON_LEAP_CORRECTION: i32 = NON_LEAP_CORRECTION[0];
32
33pub fn fixed_from_arithmetic_persian(year: i32, month: u8, day: u8) -> RataDie {
36 let p_year = i64::from(year);
37 let month = i64::from(month);
38 let day = i64::from(day);
39 let y = if p_year > 0 {
40 p_year - 474
41 } else {
42 p_year - 473
43 };
44 let year = y.rem_euclid(2820) + 474;
45
46 RataDie::new(
47 FIXED_PERSIAN_EPOCH.to_i64_date() - 1
48 + 1029983 * y.div_euclid(2820)
49 + 365 * (year - 1)
50 + (31 * year - 5).div_euclid(128)
51 + if month <= 7 {
52 31 * (month - 1)
53 } else {
54 30 * (month - 1) + 6
55 }
56 + day,
57 )
58}
59
60pub fn fixed_from_fast_persian(year: i32, month: u8, day: u8) -> RataDie {
62 let p_year = i64::from(year);
63 let month = i64::from(month);
64 let day = i64::from(day);
65 let mut new_year = FIXED_PERSIAN_EPOCH.to_i64_date() - 1
66 + 365 * (p_year - 1)
67 + (8 * p_year + 21).div_euclid(33);
68 if year > MIN_NON_LEAP_CORRECTION && NON_LEAP_CORRECTION.binary_search(&(year - 1)).is_ok() {
69 new_year -= 1;
70 }
71 RataDie::new(
72 new_year - 1
73 + if month <= 7 {
74 31 * (month - 1)
75 } else {
76 30 * (month - 1) + 6
77 }
78 + day,
79 )
80}
81
82pub fn arithmetic_persian_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> {
85 let year = arithmetic_persian_year_from_fixed(date);
86 let year = i64_to_i32(year)?;
87 #[allow(clippy::unwrap_used)] let day_of_year = 1_i64 + (date - fixed_from_arithmetic_persian(year, 1, 1));
89 #[allow(unstable_name_collisions)] let month = if day_of_year <= 186 {
91 day_of_year.div_ceil(31) as u8
92 } else {
93 (day_of_year - 6).div_ceil(30) as u8
94 };
95 let day = (date - fixed_from_arithmetic_persian(year, month, 1) + 1) as u8;
96 Ok((year, month, day))
97}
98
99pub fn fast_persian_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> {
101 let year = fast_persian_year_from_fixed(date);
102 let mut year = i64_to_i32(year)?;
103 let mut day_of_year = 1_i64 + (date - fixed_from_fast_persian(year, 1, 1));
104 if day_of_year == 366
105 && year >= MIN_NON_LEAP_CORRECTION
106 && NON_LEAP_CORRECTION.binary_search(&year).is_ok()
107 {
108 year += 1;
109 day_of_year = 1;
110 }
111 #[allow(unstable_name_collisions)] let month = if day_of_year <= 186 {
113 day_of_year.div_ceil(31) as u8
114 } else {
115 (day_of_year - 6).div_ceil(30) as u8
116 };
117 let day = (date - fixed_from_fast_persian(year, month, 1) + 1) as u8;
118 Ok((year, month, day))
119}
120
121fn arithmetic_persian_year_from_fixed(date: RataDie) -> i64 {
124 let d0 = date - fixed_from_arithmetic_persian(475, 1, 1);
125 let n2820 = d0.div_euclid(1029983);
126 let d1 = d0.rem_euclid(1029983);
127 let y2820 = if d1 == 1029982 {
128 2820
129 } else {
130 (128 * d1 + 46878).div_euclid(46751)
131 };
132 let year = 474 + n2820 * 2820 + y2820;
133 if year > 0 {
134 year
135 } else {
136 year - 1
137 }
138}
139
140fn fast_persian_year_from_fixed(date: RataDie) -> i64 {
142 let days_since_epoch = date - FIXED_PERSIAN_EPOCH + 1;
143 1 + (33 * days_since_epoch + 3).div_euclid(12053)
144}
145
146#[allow(dead_code)]
149fn is_arithmetic_leap_year(p_year: i32) -> bool {
150 let mut p_year = p_year as i64;
151 if 0 < p_year {
152 p_year -= 474;
153 } else {
154 p_year -= 473;
155 };
156 let year = p_year.rem_euclid(2820) + 474;
157
158 ((year + 38) * 31).rem_euclid(128) < 31
159}
160
161pub fn is_leap_year(p_year: i32) -> bool {
163 if p_year >= MIN_NON_LEAP_CORRECTION && NON_LEAP_CORRECTION.binary_search(&p_year).is_ok() {
164 false
165 } else if p_year > MIN_NON_LEAP_CORRECTION
166 && NON_LEAP_CORRECTION.binary_search(&(p_year - 1)).is_ok()
167 {
168 true
169 } else {
170 let p_year = p_year as i64;
171 (25 * p_year + 11).rem_euclid(33) < 8
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 #[test]
179 fn test_persian_epoch() {
180 let epoch = FIXED_PERSIAN_EPOCH.to_i64_date();
181 let epoch_year_from_fixed = crate::iso::iso_year_from_fixed(RataDie::new(epoch));
183 assert_eq!(epoch_year_from_fixed, 622);
185 }
186
187 fn nowruz(g_year: i32) -> RataDie {
189 let (y, _m, _d) = crate::iso::iso_from_fixed(FIXED_PERSIAN_EPOCH).unwrap();
190 let persian_year = g_year - y + 1;
191 let year = if persian_year <= 0 {
192 persian_year - 1
193 } else {
194 persian_year
195 };
196 fixed_from_fast_persian(year, 1, 1)
197 }
198
199 #[test]
200 fn test_nowruz() {
201 let nowruz_test_year_start = 2000;
203 let nowruz_test_year_end = 2103;
204
205 for year in nowruz_test_year_start..=nowruz_test_year_end {
206 let two_thousand_eight_to_fixed = nowruz(year).to_i64_date();
207 let iso_date = crate::iso::fixed_from_iso(year, 3, 21);
208 let (persian_year, _m, _d) = fast_persian_from_fixed(iso_date).unwrap();
209 assert_eq!(
210 fast_persian_from_fixed(RataDie::new(two_thousand_eight_to_fixed))
211 .unwrap()
212 .0,
213 persian_year
214 );
215 }
216 }
217}