/rust/registry/src/index.crates.io-1949cf8c6b5b557f/calendrical_calculations-0.1.2/src/julian.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}; |
11 | | use crate::rata_die::RataDie; |
12 | | |
13 | | // Julian epoch is equivalent to fixed_from_iso of December 30th of 0 year |
14 | | // 1st Jan of 1st year Julian is equivalent to December 30th of 0th year of ISO year |
15 | | const JULIAN_EPOCH: RataDie = RataDie::new(-1); |
16 | | |
17 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1684-L1687> |
18 | | #[inline(always)] |
19 | 0 | pub const fn is_leap_year(year: i32) -> bool { |
20 | 0 | year % 4 == 0 |
21 | 0 | } |
22 | | |
23 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1689-L1709> |
24 | 0 | pub const fn fixed_from_julian(year: i32, month: u8, day: u8) -> RataDie { |
25 | 0 | let mut fixed = |
26 | 0 | JULIAN_EPOCH.to_i64_date() - 1 + 365 * (year as i64 - 1) + (year as i64 - 1).div_euclid(4); |
27 | 0 | debug_assert!(month > 0 && month < 13, "Month should be in range 1..=12."); |
28 | 0 | fixed += match month { |
29 | 0 | 1 => 0, |
30 | 0 | 2 => 31, |
31 | 0 | 3 => 59, |
32 | 0 | 4 => 90, |
33 | 0 | 5 => 120, |
34 | 0 | 6 => 151, |
35 | 0 | 7 => 181, |
36 | 0 | 8 => 212, |
37 | 0 | 9 => 243, |
38 | 0 | 10 => 273, |
39 | 0 | 11 => 304, |
40 | 0 | 12 => 334, |
41 | 0 | _ => -1, |
42 | | }; |
43 | | // Only add one if the month is after February (month > 2), since leap days are added to the end of February |
44 | 0 | if month > 2 && is_leap_year(year) { |
45 | 0 | fixed += 1; |
46 | 0 | } |
47 | 0 | RataDie::new(fixed + (day as i64)) |
48 | 0 | } |
49 | | |
50 | | /// Lisp code reference: <https://github.com/EdReingold/calendar-code2/blob/1ee51ecfaae6f856b0d7de3e36e9042100b4f424/calendar.l#L1711-L1738> |
51 | 0 | pub fn julian_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> { |
52 | 0 | let approx = (4 * date.to_i64_date() + 1464).div_euclid(1461); |
53 | 0 | let year = i64_to_i32(approx)?; |
54 | 0 | let prior_days = date |
55 | 0 | - fixed_from_julian(year, 1, 1) |
56 | 0 | - if is_leap_year(year) && date > fixed_from_julian(year, 2, 28) { |
57 | 0 | 1 |
58 | | } else { |
59 | 0 | 0 |
60 | | }; |
61 | 0 | let adjusted_year = if prior_days >= 365 { |
62 | 0 | year.saturating_add(1) |
63 | | } else { |
64 | 0 | year |
65 | | }; |
66 | 0 | let adjusted_prior_days = prior_days.rem_euclid(365); |
67 | 0 | debug_assert!((0..365).contains(&adjusted_prior_days)); |
68 | 0 | let month = if adjusted_prior_days < 31 { |
69 | 0 | 1 |
70 | 0 | } else if adjusted_prior_days < 59 { |
71 | 0 | 2 |
72 | 0 | } else if adjusted_prior_days < 90 { |
73 | 0 | 3 |
74 | 0 | } else if adjusted_prior_days < 120 { |
75 | 0 | 4 |
76 | 0 | } else if adjusted_prior_days < 151 { |
77 | 0 | 5 |
78 | 0 | } else if adjusted_prior_days < 181 { |
79 | 0 | 6 |
80 | 0 | } else if adjusted_prior_days < 212 { |
81 | 0 | 7 |
82 | 0 | } else if adjusted_prior_days < 243 { |
83 | 0 | 8 |
84 | 0 | } else if adjusted_prior_days < 273 { |
85 | 0 | 9 |
86 | 0 | } else if adjusted_prior_days < 304 { |
87 | 0 | 10 |
88 | 0 | } else if adjusted_prior_days < 334 { |
89 | 0 | 11 |
90 | | } else { |
91 | 0 | 12 |
92 | | }; |
93 | 0 | let day = (date - fixed_from_julian(adjusted_year, month, 1) + 1) as u8; // as days_in_month is < u8::MAX |
94 | 0 | debug_assert!(day <= 31, "Day assertion failed; date: {date:?}, adjusted_year: {adjusted_year}, prior_days: {prior_days}, month: {month}, day: {day}"); |
95 | | |
96 | 0 | Ok((adjusted_year, month, day)) |
97 | 0 | } |
98 | | |
99 | | /// Get a fixed date from the ymd of a Julian date. |
100 | | /// |
101 | | /// Years are counted as in _Calendrical Calculations_ by Reingold & Dershowitz, |
102 | | /// meaning there is no year 0. For instance, near the epoch date, years are counted: -3, -2, -1, 1, 2, 3 instead of -2, -1, 0, 1, 2, 3. |
103 | | /// |
104 | | /// Primarily useful for use with code constructing epochs specified in the bookg |
105 | 0 | pub const fn fixed_from_julian_book_version(book_year: i32, month: u8, day: u8) -> RataDie { |
106 | 0 | debug_assert!(book_year != 0); |
107 | | // TODO: Should we check the bounds here? |
108 | 0 | fixed_from_julian( |
109 | 0 | if book_year < 0 { |
110 | 0 | book_year + 1 |
111 | | } else { |
112 | 0 | book_year |
113 | | }, |
114 | 0 | month, |
115 | 0 | day, |
116 | | ) |
117 | 0 | } |