bdays/
easter.rs

1use chrono::{Datelike, NaiveDate};
2use std::error;
3use std::fmt;
4
5/// Error type for easter calculation functions.
6#[derive(Debug, Clone)]
7pub struct EasterError {
8    y: i32,
9}
10
11impl EasterError {
12    fn description_string(&self) -> String {
13        let mut msg = format!("Couldn't parse easter date for year {}.", self.y);
14
15        if self.y < 1582 {
16            msg.push_str(" Current algorithm only works after year 1582.");
17        }
18
19        msg
20    }
21}
22
23impl fmt::Display for EasterError {
24    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25        write!(f, "{}", self.description_string())
26    }
27}
28
29impl error::Error for EasterError {}
30
31/// Returns easter date for year `y`
32/// as the number of days since January 1, Year 1 (aka Day 1) in the proleptic Gregorian calendar.
33pub fn easter_num_days_from_ce(y: i32) -> Result<i32, EasterError> {
34    // Algo R only works after 1582
35    if y < 1582 {
36        return Err(EasterError { y });
37    }
38
39    // Century
40    let c = (y / 100) + 1;
41
42    // Shifted Epact
43    let mut se = (14 + 11 * (y % 19) - 3 * c / 4 + (5 + 8 * c) / 25) % 30;
44
45    // Adjust Epact
46    if (se == 0) || ((se == 1) && (10 < (y % 19))) {
47        se += 1;
48    }
49
50    // Paschal Moon
51    let p = NaiveDate::from_ymd_opt(y, 4, 19)
52        .ok_or_else(|| EasterError { y })?
53        .num_days_from_ce()
54        - se;
55
56    // Easter: local the Sunday after the Paschal Moon
57    Ok(p + 7 - (p % 7))
58}
59
60/// Returns easter date for year `y`
61/// as a `chrono::NaiveDate`.
62pub fn easter_naive_date(y: i32) -> Result<NaiveDate, EasterError> {
63    let rata = easter_num_days_from_ce(y)?;
64
65    match NaiveDate::from_num_days_from_ce_opt(rata) {
66        Some(result) => Ok(result),
67        None => Err(EasterError { y }),
68    }
69}