/rust/registry/src/index.crates.io-1949cf8c6b5b557f/calendrical_calculations-0.1.2/src/persian.rs
Line | Count | Source |
1 | | // This file is part of ICU4X. |
2 | | // |
3 | | // The contents of this file implement algorithms from Calendrical Calculations |
4 | | // by Reingold & Dershowitz, Cambridge University Press, 4th edition (2018), |
5 | | // which have been released as Lisp code at <https://github.com/EdReingold/calendar-code2/> |
6 | | // under the Apache-2.0 license. Accordingly, this file is released under |
7 | | // the Apache License, Version 2.0 which can be found at the calendrical_calculations |
8 | | // package root or at http://www.apache.org/licenses/LICENSE-2.0. |
9 | | |
10 | | use crate::helpers::{i64_to_i32, I32CastError, IntegerRoundings}; |
11 | | use crate::rata_die::RataDie; |
12 | | |
13 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L4720> |
14 | | // Book states that the Persian epoch is the date: 3/19/622 and since the Persian Calendar has no year 0, the best choice was to use the Julian function. |
15 | | const FIXED_PERSIAN_EPOCH: RataDie = crate::julian::fixed_from_julian(622, 3, 19); |
16 | | |
17 | | // All these years are not leap, while they are considered leap by the 33-year |
18 | | // rule. The year following each of them is leap, but it's considered non-leap |
19 | | // by the 33-year rule. This table has been tested to match the modified |
20 | | // astronomical algorithm based on the 52.5 degrees east meridian from 1178 AP |
21 | | // (an arbitrary date before the Persian calendar was adopted in 1304 AP) to |
22 | | // 3000 AP (an arbitrary date far into the future). |
23 | | const 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 | | |
31 | | const MIN_NON_LEAP_CORRECTION: i32 = NON_LEAP_CORRECTION[0]; |
32 | | |
33 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L4803> |
34 | | /// Not used, but kept for comparative purposes |
35 | 0 | pub fn fixed_from_arithmetic_persian(year: i32, month: u8, day: u8) -> RataDie { |
36 | 0 | let p_year = i64::from(year); |
37 | 0 | let month = i64::from(month); |
38 | 0 | let day = i64::from(day); |
39 | 0 | let y = if p_year > 0 { |
40 | 0 | p_year - 474 |
41 | | } else { |
42 | 0 | p_year - 473 |
43 | | }; |
44 | 0 | let year = y.rem_euclid(2820) + 474; |
45 | | |
46 | 0 | RataDie::new( |
47 | 0 | FIXED_PERSIAN_EPOCH.to_i64_date() - 1 |
48 | 0 | + 1029983 * y.div_euclid(2820) |
49 | 0 | + 365 * (year - 1) |
50 | 0 | + (31 * year - 5).div_euclid(128) |
51 | 0 | + if month <= 7 { |
52 | 0 | 31 * (month - 1) |
53 | | } else { |
54 | 0 | 30 * (month - 1) + 6 |
55 | | } |
56 | 0 | + day, |
57 | | ) |
58 | 0 | } |
59 | | |
60 | | /// fixed_from_arithmetic_persian, modified to use the more correct 33-year rule |
61 | 0 | pub fn fixed_from_fast_persian(year: i32, month: u8, day: u8) -> RataDie { |
62 | 0 | let p_year = i64::from(year); |
63 | 0 | let month = i64::from(month); |
64 | 0 | let day = i64::from(day); |
65 | 0 | let mut new_year = FIXED_PERSIAN_EPOCH.to_i64_date() - 1 |
66 | 0 | + 365 * (p_year - 1) |
67 | 0 | + (8 * p_year + 21).div_euclid(33); |
68 | 0 | if year > MIN_NON_LEAP_CORRECTION && NON_LEAP_CORRECTION.binary_search(&(year - 1)).is_ok() { |
69 | 0 | new_year -= 1; |
70 | 0 | } |
71 | 0 | RataDie::new( |
72 | 0 | new_year - 1 |
73 | 0 | + if month <= 7 { |
74 | 0 | 31 * (month - 1) |
75 | | } else { |
76 | 0 | 30 * (month - 1) + 6 |
77 | | } |
78 | 0 | + day, |
79 | | ) |
80 | 0 | } |
81 | | |
82 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L4857> |
83 | | /// Not used, but kept for comparative purposes |
84 | 0 | pub fn arithmetic_persian_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> { |
85 | 0 | let year = arithmetic_persian_year_from_fixed(date); |
86 | 0 | let year = i64_to_i32(year)?; |
87 | | #[allow(clippy::unwrap_used)] // valid month,day |
88 | 0 | let day_of_year = 1_i64 + (date - fixed_from_arithmetic_persian(year, 1, 1)); |
89 | | #[allow(unstable_name_collisions)] // div_ceil is unstable and polyfilled |
90 | 0 | let month = if day_of_year <= 186 { |
91 | 0 | day_of_year.div_ceil(31) as u8 |
92 | | } else { |
93 | 0 | (day_of_year - 6).div_ceil(30) as u8 |
94 | | }; |
95 | 0 | let day = (date - fixed_from_arithmetic_persian(year, month, 1) + 1) as u8; |
96 | 0 | Ok((year, month, day)) |
97 | 0 | } |
98 | | |
99 | | /// arithmetic_persian_from_fixed, modified to use the 33-year rule method |
100 | 0 | pub fn fast_persian_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> { |
101 | 0 | let year = fast_persian_year_from_fixed(date); |
102 | 0 | let mut year = i64_to_i32(year)?; |
103 | 0 | let mut day_of_year = 1_i64 + (date - fixed_from_fast_persian(year, 1, 1)); |
104 | 0 | if day_of_year == 366 |
105 | 0 | && year >= MIN_NON_LEAP_CORRECTION |
106 | 0 | && NON_LEAP_CORRECTION.binary_search(&year).is_ok() |
107 | 0 | { |
108 | 0 | year += 1; |
109 | 0 | day_of_year = 1; |
110 | 0 | } |
111 | | #[allow(unstable_name_collisions)] // div_ceil is unstable and polyfilled |
112 | 0 | let month = if day_of_year <= 186 { |
113 | 0 | day_of_year.div_ceil(31) as u8 |
114 | | } else { |
115 | 0 | (day_of_year - 6).div_ceil(30) as u8 |
116 | | }; |
117 | 0 | let day = (date - fixed_from_fast_persian(year, month, 1) + 1) as u8; |
118 | 0 | Ok((year, month, day)) |
119 | 0 | } |
120 | | |
121 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L4829> |
122 | | /// Not used, but kept for comparative purposes |
123 | 0 | fn arithmetic_persian_year_from_fixed(date: RataDie) -> i64 { |
124 | 0 | let d0 = date - fixed_from_arithmetic_persian(475, 1, 1); |
125 | 0 | let n2820 = d0.div_euclid(1029983); |
126 | 0 | let d1 = d0.rem_euclid(1029983); |
127 | 0 | let y2820 = if d1 == 1029982 { |
128 | 0 | 2820 |
129 | | } else { |
130 | 0 | (128 * d1 + 46878).div_euclid(46751) |
131 | | }; |
132 | 0 | let year = 474 + n2820 * 2820 + y2820; |
133 | 0 | if year > 0 { |
134 | 0 | year |
135 | | } else { |
136 | 0 | year - 1 |
137 | | } |
138 | 0 | } |
139 | | |
140 | | /// arithmetic_persian_year_from_fixed modified for the 33-year rule |
141 | 0 | fn fast_persian_year_from_fixed(date: RataDie) -> i64 { |
142 | 0 | let days_since_epoch = date - FIXED_PERSIAN_EPOCH + 1; |
143 | 0 | 1 + (33 * days_since_epoch + 3).div_euclid(12053) |
144 | 0 | } |
145 | | |
146 | | /// Lisp code reference: https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L4789 |
147 | | /// Not used, but kept for comparative purposes |
148 | | #[allow(dead_code)] |
149 | 0 | fn is_arithmetic_leap_year(p_year: i32, _data: ()) -> bool { |
150 | 0 | let mut p_year = p_year as i64; |
151 | 0 | if 0 < p_year { |
152 | 0 | p_year -= 474; |
153 | 0 | } else { |
154 | 0 | p_year -= 473; |
155 | 0 | }; |
156 | 0 | let year = p_year.rem_euclid(2820) + 474; |
157 | | |
158 | 0 | ((year + 38) * 31).rem_euclid(128) < 31 |
159 | 0 | } |
160 | | |
161 | | /// Calculated using the 33-year rule |
162 | 0 | pub fn is_leap_year(p_year: i32, _data: ()) -> bool { |
163 | 0 | if p_year >= MIN_NON_LEAP_CORRECTION && NON_LEAP_CORRECTION.binary_search(&p_year).is_ok() { |
164 | 0 | false |
165 | 0 | } else if p_year > MIN_NON_LEAP_CORRECTION |
166 | 0 | && NON_LEAP_CORRECTION.binary_search(&(p_year - 1)).is_ok() |
167 | | { |
168 | 0 | true |
169 | | } else { |
170 | 0 | let p_year = p_year as i64; |
171 | 0 | (25 * p_year + 11).rem_euclid(33) < 8 |
172 | | } |
173 | 0 | } |
174 | | |
175 | | #[cfg(test)] |
176 | | mod tests { |
177 | | use super::*; |
178 | | #[test] |
179 | | fn test_persian_epoch() { |
180 | | let epoch = FIXED_PERSIAN_EPOCH.to_i64_date(); |
181 | | // Iso year of Persian Epoch |
182 | | let epoch_year_from_fixed = crate::iso::iso_year_from_fixed(RataDie::new(epoch)); |
183 | | // 622 is the correct ISO year for the Persian Epoch |
184 | | assert_eq!(epoch_year_from_fixed, 622); |
185 | | } |
186 | | |
187 | | // Persian New Year occurring in March of Gregorian year (g_year) to fixed date |
188 | | 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 | | // These values are used as test data in appendix C of the "Calendrical Calculations" book |
202 | | 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 | | } |