/rust/registry/src/index.crates.io-1949cf8c6b5b557f/calendrical_calculations-0.1.2/src/islamic.rs
Line | Count | Source |
1 | | use crate::astronomy::*; |
2 | | use crate::helpers::{i64_to_saturated_i32, next}; |
3 | | use crate::rata_die::{Moment, RataDie}; |
4 | | #[allow(unused_imports)] |
5 | | use core_maths::*; |
6 | | |
7 | | // Different islamic calendars use different epochs (Thursday vs Friday) due to disagreement on the exact date of Mohammed's migration to Mecca. |
8 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2066> |
9 | | const FIXED_ISLAMIC_EPOCH_FRIDAY: RataDie = crate::julian::fixed_from_julian(622, 7, 16); |
10 | | const FIXED_ISLAMIC_EPOCH_THURSDAY: RataDie = crate::julian::fixed_from_julian(622, 7, 15); |
11 | | |
12 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L6898> |
13 | | const CAIRO: Location = Location { |
14 | | latitude: 30.1, |
15 | | longitude: 31.3, |
16 | | elevation: 200.0, |
17 | | zone: (1_f64 / 12_f64), |
18 | | }; |
19 | | |
20 | | /// Common abstraction over islamic-style calendars |
21 | | pub trait IslamicBasedMarker { |
22 | | /// The epoch of the calendar. Different calendars use a different epoch (Thu or Fri) due to disagreement on the exact date of Mohammed's migration to Mecca. |
23 | | const EPOCH: RataDie; |
24 | | /// The name of the calendar for debugging. |
25 | | const DEBUG_NAME: &'static str; |
26 | | /// Whether this calendar is known to have 353-day years. |
27 | | /// This is probably a bug; see <https://github.com/unicode-org/icu4x/issues/4930> |
28 | | const HAS_353_DAY_YEARS: bool; |
29 | | /// Given the extended year, calculate the approximate new year using the mean synodic month |
30 | 0 | fn mean_synodic_ny(extended_year: i32) -> RataDie { |
31 | 0 | Self::EPOCH + (f64::from((extended_year - 1) * 12) * MEAN_SYNODIC_MONTH).floor() as i64 |
32 | 0 | } Unexecuted instantiation: <calendrical_calculations::islamic::SaudiIslamicMarker as calendrical_calculations::islamic::IslamicBasedMarker>::mean_synodic_ny Unexecuted instantiation: <calendrical_calculations::islamic::ObservationalIslamicMarker as calendrical_calculations::islamic::IslamicBasedMarker>::mean_synodic_ny Unexecuted instantiation: <_ as calendrical_calculations::islamic::IslamicBasedMarker>::mean_synodic_ny |
33 | | /// Given an iso date, calculate the *approximate* islamic year it corresponds to (for quick cache lookup) |
34 | 0 | fn approximate_islamic_from_fixed(date: RataDie) -> i32 { |
35 | 0 | let diff = date - Self::EPOCH; |
36 | 0 | let months = diff as f64 / MEAN_SYNODIC_MONTH; |
37 | 0 | let years = months / 12.; |
38 | 0 | (years + 1.).floor() as i32 |
39 | 0 | } Unexecuted instantiation: <calendrical_calculations::islamic::SaudiIslamicMarker as calendrical_calculations::islamic::IslamicBasedMarker>::approximate_islamic_from_fixed Unexecuted instantiation: <calendrical_calculations::islamic::ObservationalIslamicMarker as calendrical_calculations::islamic::IslamicBasedMarker>::approximate_islamic_from_fixed Unexecuted instantiation: <_ as calendrical_calculations::islamic::IslamicBasedMarker>::approximate_islamic_from_fixed |
40 | | /// Convert an islamic date in this calendar to a R.D. |
41 | | fn fixed_from_islamic(year: i32, month: u8, day: u8) -> RataDie; |
42 | | /// Convert an R.D. To an islamic date in this calendar |
43 | | fn islamic_from_fixed(date: RataDie) -> (i32, u8, u8); |
44 | | |
45 | | /// Given an extended year, calculate whether each month is 29 or 30 days long |
46 | 0 | fn month_lengths_for_year(extended_year: i32, ny: RataDie) -> [bool; 12] { |
47 | 0 | let next_ny = Self::fixed_from_islamic(extended_year + 1, 1, 1); |
48 | 0 | match next_ny - ny { |
49 | 0 | 355 | 354 => (), |
50 | 0 | 353 if Self::HAS_353_DAY_YEARS => { |
51 | 0 | #[cfg(feature = "logging")] |
52 | 0 | log::trace!( |
53 | 0 | "({}) Found year {extended_year} AH with length {}. See <https://github.com/unicode-org/icu4x/issues/4930>", |
54 | 0 | Self::DEBUG_NAME, |
55 | 0 | next_ny - ny |
56 | 0 | ); |
57 | 0 | } |
58 | 0 | other => { |
59 | 0 | debug_assert!( |
60 | 0 | false, |
61 | 0 | "({}) Found year {extended_year} AH with length {}!", |
62 | | Self::DEBUG_NAME, |
63 | | other |
64 | | ) |
65 | | } |
66 | | } |
67 | 0 | let mut prev_rd = ny; |
68 | 0 | let mut excess_days = 0; |
69 | 0 | let mut lengths = core::array::from_fn(|month_idx| { |
70 | 0 | let month_idx = month_idx as u8; |
71 | 0 | let new_rd = if month_idx < 11 { |
72 | 0 | Self::fixed_from_islamic(extended_year, month_idx + 2, 1) |
73 | | } else { |
74 | 0 | next_ny |
75 | | }; |
76 | 0 | let diff = new_rd - prev_rd; |
77 | 0 | prev_rd = new_rd; |
78 | 0 | match diff { |
79 | 0 | 29 => false, |
80 | 0 | 30 => true, |
81 | | 31 => { |
82 | | #[cfg(feature = "logging")] |
83 | | log::trace!( |
84 | | "({}) Found year {extended_year} AH with month length {diff} for month {}.", |
85 | | Self::DEBUG_NAME, |
86 | | month_idx + 1 |
87 | | ); |
88 | 0 | excess_days += 1; |
89 | 0 | true |
90 | | } |
91 | | _ => { |
92 | 0 | debug_assert!( |
93 | 0 | false, |
94 | 0 | "({}) Found year {extended_year} AH with month length {diff} for month {}!", |
95 | | Self::DEBUG_NAME, |
96 | 0 | month_idx + 1 |
97 | | ); |
98 | 0 | false |
99 | | } |
100 | | } |
101 | 0 | }); Unexecuted instantiation: <calendrical_calculations::islamic::SaudiIslamicMarker as calendrical_calculations::islamic::IslamicBasedMarker>::month_lengths_for_year::{closure#0} Unexecuted instantiation: <calendrical_calculations::islamic::ObservationalIslamicMarker as calendrical_calculations::islamic::IslamicBasedMarker>::month_lengths_for_year::{closure#0} Unexecuted instantiation: <_ as calendrical_calculations::islamic::IslamicBasedMarker>::month_lengths_for_year::{closure#0} |
102 | | // To maintain invariants for calendar arithmetic, if astronomy finds |
103 | | // a 31-day month, "move" the day to the first 29-day month in the |
104 | | // same year to maintain all months at 29 or 30 days. |
105 | 0 | if excess_days != 0 { |
106 | 0 | debug_assert_eq!( |
107 | | excess_days, |
108 | | 1, |
109 | 0 | "({}) Found year {extended_year} AH with more than one excess day!", |
110 | | Self::DEBUG_NAME |
111 | | ); |
112 | 0 | if let Some(l) = lengths.iter_mut().find(|l| !(**l)) { Unexecuted instantiation: <calendrical_calculations::islamic::SaudiIslamicMarker as calendrical_calculations::islamic::IslamicBasedMarker>::month_lengths_for_year::{closure#1} Unexecuted instantiation: <calendrical_calculations::islamic::ObservationalIslamicMarker as calendrical_calculations::islamic::IslamicBasedMarker>::month_lengths_for_year::{closure#1} Unexecuted instantiation: <_ as calendrical_calculations::islamic::IslamicBasedMarker>::month_lengths_for_year::{closure#1} |
113 | 0 | *l = true; |
114 | 0 | } |
115 | 0 | } |
116 | 0 | lengths |
117 | 0 | } Unexecuted instantiation: <calendrical_calculations::islamic::SaudiIslamicMarker as calendrical_calculations::islamic::IslamicBasedMarker>::month_lengths_for_year Unexecuted instantiation: <calendrical_calculations::islamic::ObservationalIslamicMarker as calendrical_calculations::islamic::IslamicBasedMarker>::month_lengths_for_year Unexecuted instantiation: <_ as calendrical_calculations::islamic::IslamicBasedMarker>::month_lengths_for_year |
118 | | } |
119 | | |
120 | | /// Marker type for observational islamic calendar, for use with [`IslamicBasedMarker`] |
121 | | #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] |
122 | | #[allow(clippy::exhaustive_structs)] // marker |
123 | | pub struct ObservationalIslamicMarker; |
124 | | |
125 | | /// Marker type for Saudi islamic calendar, for use with [`IslamicBasedMarker`] |
126 | | #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] |
127 | | #[allow(clippy::exhaustive_structs)] // marker |
128 | | pub struct SaudiIslamicMarker; |
129 | | |
130 | | /// Marker type for civil islamic calendar, for use with [`IslamicBasedMarker`] |
131 | | #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] |
132 | | #[allow(clippy::exhaustive_structs)] // marker |
133 | | pub struct CivilIslamicMarker; |
134 | | |
135 | | /// Marker type for observational islamic calendar, for use with [`IslamicBasedMarker`] |
136 | | #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] |
137 | | #[allow(clippy::exhaustive_structs)] // marker |
138 | | pub struct TabularIslamicMarker; |
139 | | |
140 | | impl IslamicBasedMarker for ObservationalIslamicMarker { |
141 | | const EPOCH: RataDie = FIXED_ISLAMIC_EPOCH_FRIDAY; |
142 | | const DEBUG_NAME: &'static str = "ObservationalIslamic"; |
143 | | const HAS_353_DAY_YEARS: bool = true; |
144 | 0 | fn fixed_from_islamic(year: i32, month: u8, day: u8) -> RataDie { |
145 | 0 | fixed_from_islamic_observational(year, month, day) |
146 | 0 | } |
147 | 0 | fn islamic_from_fixed(date: RataDie) -> (i32, u8, u8) { |
148 | 0 | observational_islamic_from_fixed(date) |
149 | 0 | } |
150 | | } |
151 | | |
152 | | impl IslamicBasedMarker for SaudiIslamicMarker { |
153 | | const EPOCH: RataDie = FIXED_ISLAMIC_EPOCH_FRIDAY; |
154 | | const DEBUG_NAME: &'static str = "SaudiIslamic"; |
155 | | const HAS_353_DAY_YEARS: bool = true; |
156 | 0 | fn fixed_from_islamic(year: i32, month: u8, day: u8) -> RataDie { |
157 | 0 | fixed_from_saudi_islamic(year, month, day) |
158 | 0 | } |
159 | 0 | fn islamic_from_fixed(date: RataDie) -> (i32, u8, u8) { |
160 | 0 | saudi_islamic_from_fixed(date) |
161 | 0 | } |
162 | | } |
163 | | |
164 | | impl IslamicBasedMarker for CivilIslamicMarker { |
165 | | const EPOCH: RataDie = FIXED_ISLAMIC_EPOCH_FRIDAY; |
166 | | const DEBUG_NAME: &'static str = "CivilIslamic"; |
167 | | const HAS_353_DAY_YEARS: bool = false; |
168 | 0 | fn fixed_from_islamic(year: i32, month: u8, day: u8) -> RataDie { |
169 | 0 | fixed_from_islamic_civil(year, month, day) |
170 | 0 | } |
171 | 0 | fn islamic_from_fixed(date: RataDie) -> (i32, u8, u8) { |
172 | 0 | islamic_civil_from_fixed(date) |
173 | 0 | } |
174 | | } |
175 | | |
176 | | impl IslamicBasedMarker for TabularIslamicMarker { |
177 | | const EPOCH: RataDie = FIXED_ISLAMIC_EPOCH_THURSDAY; |
178 | | const DEBUG_NAME: &'static str = "TabularIslamic"; |
179 | | const HAS_353_DAY_YEARS: bool = false; |
180 | 0 | fn fixed_from_islamic(year: i32, month: u8, day: u8) -> RataDie { |
181 | 0 | fixed_from_islamic_tabular(year, month, day) |
182 | 0 | } |
183 | 0 | fn islamic_from_fixed(date: RataDie) -> (i32, u8, u8) { |
184 | 0 | islamic_tabular_from_fixed(date) |
185 | 0 | } |
186 | | } |
187 | | |
188 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L6904> |
189 | 0 | pub fn fixed_from_islamic_observational(year: i32, month: u8, day: u8) -> RataDie { |
190 | 0 | let year = i64::from(year); |
191 | 0 | let month = i64::from(month); |
192 | 0 | let day = i64::from(day); |
193 | 0 | let midmonth = FIXED_ISLAMIC_EPOCH_FRIDAY.to_f64_date() |
194 | 0 | + (((year - 1) as f64) * 12.0 + month as f64 - 0.5) * MEAN_SYNODIC_MONTH; |
195 | 0 | let lunar_phase = Astronomical::calculate_new_moon_at_or_before(RataDie::new(midmonth as i64)); |
196 | 0 | Astronomical::phasis_on_or_before(RataDie::new(midmonth as i64), CAIRO, Some(lunar_phase)) + day |
197 | 0 | - 1 |
198 | 0 | } |
199 | | |
200 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L6983-L6995> |
201 | 0 | pub fn observational_islamic_from_fixed(date: RataDie) -> (i32, u8, u8) { |
202 | 0 | let lunar_phase = Astronomical::calculate_new_moon_at_or_before(date); |
203 | 0 | let crescent = Astronomical::phasis_on_or_before(date, CAIRO, Some(lunar_phase)); |
204 | 0 | let elapsed_months = |
205 | 0 | ((crescent - FIXED_ISLAMIC_EPOCH_FRIDAY) as f64 / MEAN_SYNODIC_MONTH).round() as i32; |
206 | 0 | let year = elapsed_months.div_euclid(12) + 1; |
207 | 0 | let month = elapsed_months.rem_euclid(12) + 1; |
208 | 0 | let day = (date - crescent + 1) as u8; |
209 | | |
210 | 0 | (year, month as u8, day) |
211 | 0 | } |
212 | | |
213 | | // Saudi visibility criterion on eve of fixed date in Mecca. |
214 | | // The start of the new month only happens if both of these criterias are met: The moon is a waxing crescent at sunset of the previous day |
215 | | // and the moon sets after the sun on that same evening. |
216 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L6957> |
217 | 0 | fn saudi_criterion(date: RataDie) -> Option<bool> { |
218 | 0 | let sunset = Astronomical::sunset((date - 1).as_moment(), MECCA)?; |
219 | 0 | let tee = Location::universal_from_standard(sunset, MECCA); |
220 | 0 | let phase = Astronomical::lunar_phase(tee, Astronomical::julian_centuries(tee)); |
221 | 0 | let moonlag = Astronomical::moonlag((date - 1).as_moment(), MECCA)?; |
222 | | |
223 | 0 | Some(phase > 0.0 && phase < 90.0 && moonlag > 0.0) |
224 | 0 | } |
225 | | |
226 | 0 | pub(crate) fn adjusted_saudi_criterion(date: RataDie) -> bool { |
227 | 0 | saudi_criterion(date).unwrap_or_default() |
228 | 0 | } |
229 | | |
230 | | // Closest fixed date on or before date when Saudi visibility criterion is held. |
231 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L6966> |
232 | 0 | pub fn saudi_new_month_on_or_before(date: RataDie) -> RataDie { |
233 | 0 | let last_new_moon = (Astronomical::lunar_phase_at_or_before(0.0, date.as_moment())) |
234 | 0 | .inner() |
235 | 0 | .floor(); // Gets the R.D Date of the prior new moon |
236 | 0 | let age = date.to_f64_date() - last_new_moon; |
237 | | // Explanation of why the value 3.0 is chosen: https://github.com/unicode-org/icu4x/pull/3673/files#r1267460916 |
238 | 0 | let tau = if age <= 3.0 && !adjusted_saudi_criterion(date) { |
239 | | // Checks if the criterion is not yet visible on the evening of date |
240 | 0 | last_new_moon - 30.0 // Goes back a month |
241 | | } else { |
242 | 0 | last_new_moon |
243 | | }; |
244 | | |
245 | 0 | next(RataDie::new(tau as i64), adjusted_saudi_criterion) // Loop that increments the day and checks if the criterion is now visible |
246 | 0 | } |
247 | | |
248 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L6996> |
249 | 0 | pub fn saudi_islamic_from_fixed(date: RataDie) -> (i32, u8, u8) { |
250 | 0 | let crescent = saudi_new_month_on_or_before(date); |
251 | 0 | let elapsed_months = |
252 | 0 | ((crescent - FIXED_ISLAMIC_EPOCH_FRIDAY) as f64 / MEAN_SYNODIC_MONTH).round() as i64; |
253 | 0 | let year = i64_to_saturated_i32(elapsed_months.div_euclid(12) + 1); |
254 | 0 | let month = (elapsed_months.rem_euclid(12) + 1) as u8; |
255 | 0 | let day = ((date - crescent) + 1) as u8; |
256 | | |
257 | 0 | (year, month, day) |
258 | 0 | } |
259 | | |
260 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L6981> |
261 | 0 | pub fn fixed_from_saudi_islamic(year: i32, month: u8, day: u8) -> RataDie { |
262 | 0 | let midmonth = RataDie::new( |
263 | 0 | FIXED_ISLAMIC_EPOCH_FRIDAY.to_i64_date() |
264 | 0 | + (((year as f64 - 1.0) * 12.0 + month as f64 - 0.5) * MEAN_SYNODIC_MONTH).floor() |
265 | 0 | as i64, |
266 | | ); |
267 | 0 | let first_day_of_month = saudi_new_month_on_or_before(midmonth).to_i64_date(); |
268 | | |
269 | 0 | RataDie::new(first_day_of_month + day as i64 - 1) |
270 | 0 | } |
271 | | |
272 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2076> |
273 | 0 | pub fn fixed_from_islamic_civil(year: i32, month: u8, day: u8) -> RataDie { |
274 | 0 | let year = i64::from(year); |
275 | 0 | let month = i64::from(month); |
276 | 0 | let day = i64::from(day); |
277 | | |
278 | 0 | RataDie::new( |
279 | 0 | (FIXED_ISLAMIC_EPOCH_FRIDAY.to_i64_date() - 1) |
280 | 0 | + (year - 1) * 354 |
281 | 0 | + (3 + year * 11).div_euclid(30) |
282 | 0 | + 29 * (month - 1) |
283 | 0 | + month.div_euclid(2) |
284 | 0 | + day, |
285 | | ) |
286 | 0 | } |
287 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2090> |
288 | 0 | pub fn islamic_civil_from_fixed(date: RataDie) -> (i32, u8, u8) { |
289 | 0 | let year = |
290 | 0 | i64_to_saturated_i32(((date - FIXED_ISLAMIC_EPOCH_FRIDAY) * 30 + 10646).div_euclid(10631)); |
291 | 0 | let prior_days = date.to_f64_date() - fixed_from_islamic_civil(year, 1, 1).to_f64_date(); |
292 | 0 | debug_assert!(prior_days >= 0.0); |
293 | 0 | debug_assert!(prior_days <= 354.); |
294 | 0 | let month = (((prior_days * 11.0) + 330.0) / 325.0) as u8; // Prior days is maximum 354 (when year length is 355), making the value always less than 12 |
295 | 0 | debug_assert!(month <= 12); |
296 | 0 | let day = |
297 | 0 | (date.to_f64_date() - fixed_from_islamic_civil(year, month, 1).to_f64_date() + 1.0) as u8; // The value will always be number between 1-30 because of the difference between the date and lunar ordinals function. |
298 | | |
299 | 0 | (year, month, day) |
300 | 0 | } |
301 | | |
302 | | /// Lisp code reference:https: //github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2076 |
303 | 0 | pub fn fixed_from_islamic_tabular(year: i32, month: u8, day: u8) -> RataDie { |
304 | 0 | let year = i64::from(year); |
305 | 0 | let month = i64::from(month); |
306 | 0 | let day = i64::from(day); |
307 | 0 | RataDie::new( |
308 | 0 | (FIXED_ISLAMIC_EPOCH_THURSDAY.to_i64_date() - 1) |
309 | 0 | + (year - 1) * 354 |
310 | 0 | + (3 + year * 11).div_euclid(30) |
311 | 0 | + 29 * (month - 1) |
312 | 0 | + month.div_euclid(2) |
313 | 0 | + day, |
314 | | ) |
315 | 0 | } |
316 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L2090> |
317 | 0 | pub fn islamic_tabular_from_fixed(date: RataDie) -> (i32, u8, u8) { |
318 | 0 | let year = i64_to_saturated_i32( |
319 | 0 | ((date - FIXED_ISLAMIC_EPOCH_THURSDAY) * 30 + 10646).div_euclid(10631), |
320 | | ); |
321 | 0 | let prior_days = date.to_f64_date() - fixed_from_islamic_tabular(year, 1, 1).to_f64_date(); |
322 | 0 | debug_assert!(prior_days >= 0.0); |
323 | 0 | debug_assert!(prior_days <= 354.); |
324 | 0 | let month = (((prior_days * 11.0) + 330.0) / 325.0) as u8; // Prior days is maximum 354 (when year length is 355), making the value always less than 12 |
325 | 0 | debug_assert!(month <= 12); |
326 | 0 | let day = |
327 | 0 | (date.to_f64_date() - fixed_from_islamic_tabular(year, month, 1).to_f64_date() + 1.0) as u8; // The value will always be number between 1-30 because of the difference between the date and lunar ordinals function. |
328 | | |
329 | 0 | (year, month, day) |
330 | 0 | } |
331 | | |
332 | | /// The number of days in a month for the observational islamic calendar |
333 | 0 | pub fn observational_islamic_month_days(year: i32, month: u8) -> u8 { |
334 | 0 | let midmonth = FIXED_ISLAMIC_EPOCH_FRIDAY.to_f64_date() |
335 | 0 | + (((year - 1) as f64) * 12.0 + month as f64 - 0.5) * MEAN_SYNODIC_MONTH; |
336 | | |
337 | 0 | let lunar_phase: f64 = |
338 | 0 | Astronomical::calculate_new_moon_at_or_before(RataDie::new(midmonth as i64)); |
339 | 0 | let f_date = |
340 | 0 | Astronomical::phasis_on_or_before(RataDie::new(midmonth as i64), CAIRO, Some(lunar_phase)); |
341 | | |
342 | 0 | Astronomical::month_length(f_date, CAIRO) |
343 | 0 | } |
344 | | |
345 | | /// The number of days in a month for the Saudi (Umm Al-Qura) calendar |
346 | 0 | pub fn saudi_islamic_month_days(year: i32, month: u8) -> u8 { |
347 | | // We cannot use month_days from the book here, that is for the observational calendar |
348 | | // |
349 | | // Instead we subtract the two new months calculated using the saudi criterion |
350 | 0 | let midmonth = Moment::new( |
351 | 0 | FIXED_ISLAMIC_EPOCH_FRIDAY.to_f64_date() |
352 | 0 | + (((year - 1) as f64) * 12.0 + month as f64 - 0.5) * MEAN_SYNODIC_MONTH, |
353 | | ); |
354 | 0 | let midmonth_next = midmonth + MEAN_SYNODIC_MONTH; |
355 | | |
356 | 0 | let month_start = saudi_new_month_on_or_before(midmonth.as_rata_die()); |
357 | 0 | let next_month_start = saudi_new_month_on_or_before(midmonth_next.as_rata_die()); |
358 | | |
359 | 0 | let diff = next_month_start - month_start; |
360 | 0 | debug_assert!( |
361 | 0 | diff <= 30, |
362 | 0 | "umm-al-qura months must not be more than 30 days" |
363 | | ); |
364 | 0 | u8::try_from(diff).unwrap_or(30) |
365 | 0 | } |
366 | | |
367 | | #[cfg(test)] |
368 | | mod tests { |
369 | | use super::*; |
370 | | |
371 | | static TEST_FIXED_DATE: [i64; 33] = [ |
372 | | -214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605, |
373 | | 470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554, |
374 | | 664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652, |
375 | | ]; |
376 | | // Removed: 601716 and 727274 fixed dates |
377 | | static TEST_FIXED_DATE_UMMALQURA: [i64; 31] = [ |
378 | | -214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605, |
379 | | 470160, 473837, 507850, 524156, 544676, 567118, 569477, 613424, 626596, 645554, 664224, |
380 | | 671401, 694799, 704424, 708842, 709409, 709580, 728714, 744313, 764652, |
381 | | ]; |
382 | | // Values from lisp code |
383 | | static SAUDI_CRITERION_EXPECTED: [bool; 33] = [ |
384 | | false, false, true, false, false, true, false, true, false, false, true, false, false, |
385 | | true, true, true, true, false, false, true, true, true, false, false, false, false, false, |
386 | | false, true, false, true, false, true, |
387 | | ]; |
388 | | // Values from lisp code, removed two expected months. |
389 | | static SAUDI_NEW_MONTH_OR_BEFORE_EXPECTED: [f64; 31] = [ |
390 | | -214203.0, -61412.0, 25467.0, 49210.0, 171290.0, 210152.0, 253414.0, 369735.0, 400063.0, |
391 | | 434348.0, 452598.0, 470139.0, 473830.0, 507850.0, 524150.0, 544674.0, 567118.0, 569450.0, |
392 | | 613421.0, 626592.0, 645551.0, 664214.0, 671391.0, 694779.0, 704405.0, 708835.0, 709396.0, |
393 | | 709573.0, 728709.0, 744301.0, 764647.0, |
394 | | ]; |
395 | | #[test] |
396 | | fn test_islamic_epoch_friday() { |
397 | | let epoch = FIXED_ISLAMIC_EPOCH_FRIDAY.to_i64_date(); |
398 | | // Iso year of Islamic Epoch |
399 | | let epoch_year_from_fixed = crate::iso::iso_year_from_fixed(RataDie::new(epoch)); |
400 | | // 622 is the correct ISO year for the Islamic Epoch |
401 | | assert_eq!(epoch_year_from_fixed, 622); |
402 | | } |
403 | | |
404 | | #[test] |
405 | | fn test_islamic_epoch_thursday() { |
406 | | let epoch = FIXED_ISLAMIC_EPOCH_THURSDAY.to_i64_date(); |
407 | | // Iso year of Islamic Epoch |
408 | | let epoch_year_from_fixed = crate::iso::iso_year_from_fixed(RataDie::new(epoch)); |
409 | | // 622 is the correct ISO year for the Islamic Epoch |
410 | | assert_eq!(epoch_year_from_fixed, 622); |
411 | | } |
412 | | |
413 | | #[test] |
414 | | fn test_saudi_criterion() { |
415 | | for (boolean, f_date) in SAUDI_CRITERION_EXPECTED.iter().zip(TEST_FIXED_DATE.iter()) { |
416 | | let bool_result = saudi_criterion(RataDie::new(*f_date)).unwrap(); |
417 | | assert_eq!(*boolean, bool_result, "{f_date:?}"); |
418 | | } |
419 | | } |
420 | | |
421 | | #[test] |
422 | | fn test_saudi_new_month_or_before() { |
423 | | for (date, f_date) in SAUDI_NEW_MONTH_OR_BEFORE_EXPECTED |
424 | | .iter() |
425 | | .zip(TEST_FIXED_DATE_UMMALQURA.iter()) |
426 | | { |
427 | | let date_result = saudi_new_month_on_or_before(RataDie::new(*f_date)).to_f64_date(); |
428 | | assert_eq!(*date, date_result, "{f_date:?}"); |
429 | | } |
430 | | } |
431 | | } |