rabbit_time/
arvelie.rs

1use std::fmt::{Debug, Display};
2
3use chrono::{Datelike, NaiveDate};
4
5/// Represents a date on the [Arvelie](https://wiki.xxiivv.com/site/arvelie.html) calendar.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
7pub struct ArvelieDate {
8    /// The Gregorian calendar-year at which you began counting.
9    pub start_year: i32,
10    /// The year, as of whenever you started counting.
11    pub year: i32,
12    /// The day of the year.
13    pub sub_year: SubYear,
14}
15impl ArvelieDate {
16    /// Returns the 1-indexed day of the year.
17    pub fn day_of_year(&self) -> u16 {
18        match self.sub_year {
19            SubYear::LeapDay => 366,
20            SubYear::YearDay => 365,
21            SubYear::NormalSubYear(sub_year) => {
22                sub_year.day as u16 + sub_year.month as u16 * 14 + 1
23            }
24        }
25    }
26    /// Returns the year, on the Gregorian calendar.
27    pub fn gregorian_year(&self) -> i32 {
28        self.start_year + self.year
29    }
30}
31impl Display for ArvelieDate {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        write!(f, "{:0>2}", self.year)?;
34        write!(f, "{}", self.sub_year)
35    }
36}
37
38/// The day and month of an [ArvelieDate].
39#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
40pub enum SubYear {
41    /// A normal day/month pair.
42    NormalSubYear(NormalSubYear),
43    /// Year day. The last day of a normal year.
44    YearDay,
45    /// Leap day. The last day of a leap year.
46    LeapDay,
47}
48impl Default for SubYear {
49    fn default() -> Self {
50        SubYear::NormalSubYear(NormalSubYear::default())
51    }
52}
53impl Display for SubYear {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        match self {
56            SubYear::NormalSubYear(normal_sub_year) => write!(f, "{normal_sub_year}"),
57            SubYear::YearDay => f.write_str("+00"),
58            SubYear::LeapDay => f.write_str("+01"),
59        }
60    }
61}
62
63/// A normal day/month pair for an [ArvelieDate].
64#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
65pub struct NormalSubYear {
66    /// The month of the year. There are 26 months in a year.
67    pub month: u8,
68    /// The day of the month. There are 14 days in a month.
69    pub day: u8,
70}
71impl Display for NormalSubYear {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect::<Vec<char>>();
74        let letter = chars.get(self.month as usize).unwrap();
75        write!(f, "{letter}{:0>2}", self.day)
76    }
77}
78
79/// The inputs for converting a [NaiveDate] to an [ArvelieDate].
80pub struct NaiveDateAndStartYear {
81    /// The date to be converted.
82    pub date: NaiveDate,
83    /// The year at which the converted date will have started counting.
84    pub start_year: i32,
85}
86
87impl From<NaiveDateAndStartYear> for ArvelieDate {
88    fn from(value: NaiveDateAndStartYear) -> Self {
89        let NaiveDateAndStartYear { date, start_year } = value;
90        let year = date.year() - start_year;
91        let day_of_year = date.ordinal0() as i32;
92        let sub_year = match day_of_year {
93            365 => SubYear::LeapDay,
94            364 => SubYear::YearDay,
95            _ => {
96                let month = day_of_year / 14;
97                let day = day_of_year - month * 14;
98                SubYear::NormalSubYear(NormalSubYear {
99                    month: month as u8,
100                    day: day as u8,
101                })
102            }
103        };
104
105        ArvelieDate {
106            start_year,
107            year,
108            sub_year,
109        }
110    }
111}
112
113impl From<ArvelieDate> for NaiveDate {
114    fn from(value: ArvelieDate) -> Self {
115        NaiveDate::from_yo_opt(value.gregorian_year(), value.day_of_year() as u32).unwrap()
116    }
117}