calendrical_calculations/
julian.rs1use crate::helpers::{i64_to_i32, k_day_after, I32CastError};
11use crate::rata_die::RataDie;
12
13const JULIAN_EPOCH: RataDie = RataDie::new(-1);
16
17#[inline(always)]
19pub const fn is_leap_year(year: i32) -> bool {
20 year % 4 == 0
21}
22
23pub const fn fixed_from_julian(year: i32, month: u8, day: u8) -> RataDie {
25 let mut fixed =
26 JULIAN_EPOCH.to_i64_date() - 1 + 365 * (year as i64 - 1) + (year as i64 - 1).div_euclid(4);
27 debug_assert!(month > 0 && month < 13, "Month should be in range 1..=12.");
28 fixed += match month {
29 1 => 0,
30 2 => 31,
31 3 => 59,
32 4 => 90,
33 5 => 120,
34 6 => 151,
35 7 => 181,
36 8 => 212,
37 9 => 243,
38 10 => 273,
39 11 => 304,
40 12 => 334,
41 _ => -1,
42 };
43 if month > 2 && is_leap_year(year) {
45 fixed += 1;
46 }
47 RataDie::new(fixed + (day as i64))
48}
49
50pub fn julian_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> {
52 let approx = (4 * date.to_i64_date() + 1464).div_euclid(1461);
53 let year = i64_to_i32(approx)?;
54 let prior_days = date
55 - fixed_from_julian(year, 1, 1)
56 - if is_leap_year(year) && date > fixed_from_julian(year, 2, 28) {
57 1
58 } else {
59 0
60 };
61 let adjusted_year = if prior_days >= 365 {
62 year.saturating_add(1)
63 } else {
64 year
65 };
66 let adjusted_prior_days = prior_days.rem_euclid(365);
67 debug_assert!((0..365).contains(&adjusted_prior_days));
68 let month = if adjusted_prior_days < 31 {
69 1
70 } else if adjusted_prior_days < 59 {
71 2
72 } else if adjusted_prior_days < 90 {
73 3
74 } else if adjusted_prior_days < 120 {
75 4
76 } else if adjusted_prior_days < 151 {
77 5
78 } else if adjusted_prior_days < 181 {
79 6
80 } else if adjusted_prior_days < 212 {
81 7
82 } else if adjusted_prior_days < 243 {
83 8
84 } else if adjusted_prior_days < 273 {
85 9
86 } else if adjusted_prior_days < 304 {
87 10
88 } else if adjusted_prior_days < 334 {
89 11
90 } else {
91 12
92 };
93 let day = (date - fixed_from_julian(adjusted_year, month, 1) + 1) as u8; debug_assert!(day <= 31, "Day assertion failed; date: {date:?}, adjusted_year: {adjusted_year}, prior_days: {prior_days}, month: {month}, day: {day}");
95
96 Ok((adjusted_year, month, day))
97}
98
99pub const fn fixed_from_julian_book_version(book_year: i32, month: u8, day: u8) -> RataDie {
106 debug_assert!(book_year != 0);
107 fixed_from_julian(
109 if book_year < 0 {
110 book_year + 1
111 } else {
112 book_year
113 },
114 month,
115 day,
116 )
117}
118
119pub fn easter(year: i32) -> RataDie {
121 let shifted_epact = (14 + 11 * year.rem_euclid(19)) % 30;
122 let paschal_moon = fixed_from_julian(year, 4, 19) - shifted_epact as i64;
123 k_day_after(0, paschal_moon)
124}
125
126#[test]
127fn test_easter() {
128 for (y, m, d) in [
130 (2021, 5, 2),
131 (2022, 4, 24),
132 (2023, 4, 16),
133 (2024, 5, 5),
134 (2025, 4, 20),
135 (2026, 4, 12),
136 (2027, 5, 2),
137 (2028, 4, 16),
138 (2029, 4, 8),
139 ] {
140 assert_eq!(easter(y), crate::iso::fixed_from_iso(y, m, d));
141 }
142}