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 RataDie::new(fixed)
100}
101
102pub 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 fn gregorian_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> {
117 let year = year_from_fixed(date)?;
118 let day_of_year = date - day_before_year(year);
119 let (month, day) = year_day(year, day_of_year as u16);
120 Ok((year, month, day))
121}
122
123pub fn easter(year: i32) -> RataDie {
125 let century = (year / 100) + 1;
126 let shifted_epact =
127 (14 + 11 * year.rem_euclid(19) - century * 3 / 4 + (5 + 8 * century) / 25).rem_euclid(30);
128 let adjusted_epact = shifted_epact
129 + (shifted_epact == 0 || (shifted_epact == 1 && 10 < year.rem_euclid(19))) as i32;
130 let paschal_moon = fixed_from_gregorian(year, 4, 19) - adjusted_epact as i64;
131
132 k_day_after(0, paschal_moon)
133}
134
135#[test]
136fn test_easter() {
137 for (y, m, d) in [
139 (2021, 4, 4),
140 (2022, 4, 17),
141 (2023, 4, 9),
142 (2024, 3, 31),
143 (2025, 4, 20),
144 (2026, 4, 5),
145 (2027, 3, 28),
146 (2028, 4, 16),
147 (2029, 4, 1),
148 ] {
149 assert_eq!(easter(y), fixed_from_gregorian(y, m, d));
150 }
151}