calendrical_calculations/
gregorian.rs1use crate::helpers::{i64_to_i32, k_day_after, I32CastError};
11use crate::rata_die::RataDie;
12
13const EPOCH: RataDie = RataDie::new(1);
15
16const DAYS_IN_YEAR: i64 = 365;
17
18const DAYS_IN_4_YEAR_CYCLE: i64 = DAYS_IN_YEAR * 4 + 1;
20
21const DAYS_IN_100_YEAR_CYCLE: i64 = 25 * DAYS_IN_4_YEAR_CYCLE - 1;
23
24pub const DAYS_IN_400_YEAR_CYCLE: i64 = 4 * DAYS_IN_100_YEAR_CYCLE + 1;
27
28pub const fn is_leap_year(year: i32) -> bool {
32 if year % 25 != 0 {
34 year % 4 == 0
35 } else {
36 year % 16 == 0
37 }
38}
39
40pub const fn fixed_from_gregorian(year: i32, month: u8, day: u8) -> RataDie {
45 day_before_year(year)
46 .add(days_before_month(year, month) as i64)
47 .add(day as i64)
48}
49
50pub const fn days_before_month(year: i32, month: u8) -> u16 {
54 if month < 3 {
55 if month == 1 {
57 0
58 } else {
59 31
60 }
61 } else {
62 31 + 28 + is_leap_year(year) as u16 + ((979 * (month as u32) - 2919) >> 5) as u16
63 }
64}
65
66pub const fn year_from_fixed(date: RataDie) -> Result<i32, I32CastError> {
68 let date = date.since(EPOCH);
70
71 let (n_400, date) = (
72 date.div_euclid(DAYS_IN_400_YEAR_CYCLE),
73 date.rem_euclid(DAYS_IN_400_YEAR_CYCLE),
74 );
75
76 let (n_100, date) = (date / DAYS_IN_100_YEAR_CYCLE, date % DAYS_IN_100_YEAR_CYCLE);
77
78 let (n_4, date) = (date / DAYS_IN_4_YEAR_CYCLE, date % DAYS_IN_4_YEAR_CYCLE);
79
80 let n_1 = date / DAYS_IN_YEAR;
81
82 let year = 400 * n_400 + 100 * n_100 + 4 * n_4 + n_1 + (n_100 != 4 && n_1 != 4) as i64;
83
84 i64_to_i32(year)
85}
86
87pub const fn day_before_year(year: i32) -> RataDie {
89 let prev_year = (year as i64) - 1;
90 let mut fixed: i64 = DAYS_IN_YEAR * prev_year;
92 const YEAR_SHIFT: i64 = (-(i32::MIN as i64 - 1) / 400 + 1) * 400;
96 fixed += (prev_year + YEAR_SHIFT) / 4 - (prev_year + YEAR_SHIFT) / 100
97 + (prev_year + YEAR_SHIFT) / 400
98 - const { YEAR_SHIFT / 4 - YEAR_SHIFT / 100 + YEAR_SHIFT / 400 };
99 EPOCH.add(fixed - 1)
100}
101
102pub const fn year_day(year: i32, day_of_year: u16) -> (u8, u8) {
104 let correction = if day_of_year < 31 + 28 + is_leap_year(year) as u16 {
106 -1
107 } else {
108 (!is_leap_year(year)) as i32
109 };
110 let month = ((12 * (day_of_year as i32 + correction) + 373) / 367) as u8; let day = (day_of_year - days_before_month(year, month)) as u8; (month, day)
113}
114
115pub const fn gregorian_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> {
117 let year = match year_from_fixed(date) {
118 Ok(y) => y,
119 Err(e) => return Err(e),
120 };
121
122 let day_of_year = date.to_i64_date() - day_before_year(year).to_i64_date();
123 let (month, day) = year_day(year, day_of_year as u16);
124 Ok((year, month, day))
125}
126
127pub const fn easter(year: i32) -> RataDie {
129 let century = (year / 100) + 1;
130 let shifted_epact =
131 (14 + 11 * year.rem_euclid(19) - century * 3 / 4 + (5 + 8 * century) / 25).rem_euclid(30);
132 let adjusted_epact = shifted_epact
133 + (shifted_epact == 0 || (shifted_epact == 1 && 10 < year.rem_euclid(19))) as i32;
134 let paschal_moon =
135 RataDie::new(fixed_from_gregorian(year, 4, 19).to_i64_date() - adjusted_epact as i64);
136
137 k_day_after(0, paschal_moon)
138}
139
140#[test]
141fn test_easter() {
142 for (y, m, d) in [
144 (2021, 4, 4),
145 (2022, 4, 17),
146 (2023, 4, 9),
147 (2024, 3, 31),
148 (2025, 4, 20),
149 (2026, 4, 5),
150 (2027, 3, 28),
151 (2028, 4, 16),
152 (2029, 4, 1),
153 ] {
154 assert_eq!(easter(y), fixed_from_gregorian(y, m, d));
155 }
156}