calendrical_calculations/
iso.rs1use crate::helpers::{i64_to_i32, k_day_after, I32CastError};
11use crate::rata_die::RataDie;
12
13const EPOCH: RataDie = RataDie::new(1);
15
16pub const fn is_leap_year(year: i32) -> bool {
20 if year % 25 != 0 {
22 year % 4 == 0
23 } else {
24 year % 16 == 0
25 }
26}
27
28pub const fn const_fixed_from_iso(year: i32, month: u8, day: u8) -> RataDie {
33 day_before_year(year)
34 .add(days_before_month(year, month) as i64)
35 .add(day as i64)
36}
37
38pub const fn days_before_month(year: i32, month: u8) -> u16 {
42 if month < 3 {
43 if month == 1 {
45 0
46 } else {
47 31
48 }
49 } else {
50 31 + 28 + is_leap_year(year) as u16 + ((979 * (month as u32) - 2919) >> 5) as u16
51 }
52}
53
54pub fn fixed_from_iso(year: i32, month: u8, day: u8) -> RataDie {
56 const_fixed_from_iso(year, month, day)
57}
58
59pub const fn iso_year_from_fixed(date: RataDie) -> Result<i32, I32CastError> {
61 let date = date.since(EPOCH);
63
64 let (n_400, date) = (date.div_euclid(146097), date.rem_euclid(146097));
66
67 let (n_100, date) = (date / 36524, date % 36524);
69
70 let (n_4, date) = (date / 1461, date % 1461);
72
73 let n_1 = date / 365;
74
75 let year = 400 * n_400 + 100 * n_100 + 4 * n_4 + n_1;
76
77 if n_100 == 4 || n_1 == 4 {
78 i64_to_i32(year)
79 } else {
80 i64_to_i32(year + 1)
81 }
82}
83
84pub const fn day_before_year(year: i32) -> RataDie {
86 let prev_year = (year as i64) - 1;
87 let mut fixed: i64 = 365 * prev_year;
89 const YEAR_SHIFT: i64 = (-(i32::MIN as i64 - 1) / 400 + 1) * 400;
93 fixed += (prev_year + YEAR_SHIFT) / 4 - (prev_year + YEAR_SHIFT) / 100
94 + (prev_year + YEAR_SHIFT) / 400
95 - const { YEAR_SHIFT / 4 - YEAR_SHIFT / 100 + YEAR_SHIFT / 400 };
96 RataDie::new(fixed)
97}
98
99pub fn year_day(year: i32, day_of_year: u16) -> (u8, u8) {
101 let correction = if day_of_year < 31 + 28 + is_leap_year(year) as u16 {
103 -1
104 } else {
105 (!is_leap_year(year)) as i32
106 };
107 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)
110}
111
112pub fn iso_from_fixed(date: RataDie) -> Result<(i32, u8, u8), I32CastError> {
114 let year = iso_year_from_fixed(date)?;
115 let day_of_year = date - day_before_year(year);
116 let (month, day) = year_day(year, day_of_year as u16);
117 Ok((year, month, day))
118}
119
120pub fn easter(year: i32) -> RataDie {
122 let century = (year / 100) + 1;
123 let shifted_epact =
124 (14 + 11 * year.rem_euclid(19) - century * 3 / 4 + (5 + 8 * century) / 25).rem_euclid(30);
125 let adjusted_epact = shifted_epact
126 + (shifted_epact == 0 || (shifted_epact == 1 && 10 < year.rem_euclid(19))) as i32;
127 let paschal_moon = fixed_from_iso(year, 4, 19) - adjusted_epact as i64;
128
129 k_day_after(0, paschal_moon)
130}
131
132#[test]
133fn test_easter() {
134 for (y, m, d) in [
136 (2021, 4, 4),
137 (2022, 4, 17),
138 (2023, 4, 9),
139 (2024, 3, 31),
140 (2025, 4, 20),
141 (2026, 4, 5),
142 (2027, 3, 28),
143 (2028, 4, 16),
144 (2029, 4, 1),
145 ] {
146 assert_eq!(easter(y), fixed_from_iso(y, m, d));
147 }
148}