Skip to main content

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 SubYear {
49    /// Returns the month as a number from 0 to 26.
50    pub fn month(&self) -> u8 {
51        match self {
52            SubYear::NormalSubYear(normal_sub_year) => normal_sub_year.month,
53            SubYear::YearDay | SubYear::LeapDay => 26,
54        }
55    }
56    /// Returns the month as a letter of the alphabet.
57    pub fn format_month(&self) -> String {
58        match self {
59            SubYear::NormalSubYear(normal_sub_year) => normal_sub_year.format_month(),
60            SubYear::YearDay | SubYear::LeapDay => "+".to_string(),
61        }
62    }
63    /// Returns the day of the month as a number from 0 to 13.
64    pub fn day(&self) -> u8 {
65        match self {
66            SubYear::NormalSubYear(normal_sub_year) => normal_sub_year.day,
67            SubYear::YearDay => 0,
68            SubYear::LeapDay => 1,
69        }
70    }
71    /// Returns the day of the month, formatted as a string.
72    pub fn format_day(&self) -> String {
73        format!("{:0>2}", self.day())
74    }
75}
76impl Default for SubYear {
77    fn default() -> Self {
78        SubYear::NormalSubYear(NormalSubYear::default())
79    }
80}
81impl Display for SubYear {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        match self {
84            SubYear::NormalSubYear(normal_sub_year) => write!(f, "{normal_sub_year}"),
85            SubYear::YearDay => f.write_str("+00"),
86            SubYear::LeapDay => f.write_str("+01"),
87        }
88    }
89}
90
91/// A normal day/month pair for an [ArvelieDate].
92#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
93pub struct NormalSubYear {
94    /// The month of the year. There are 26 months in a year.
95    pub month: u8,
96    /// The day of the month. There are 14 days in a month.
97    pub day: u8,
98}
99impl NormalSubYear {
100    /// Returns the month as a letter of the alphabet.
101    pub fn format_month(&self) -> String {
102        let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect::<Vec<char>>();
103        let letter = chars.get(self.month as usize).unwrap();
104        letter.to_string()
105    }
106}
107impl Display for NormalSubYear {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        write!(f, "{}{:0>2}", self.format_month(), self.day)
110    }
111}
112
113/// The inputs for converting a [NaiveDate] to an [ArvelieDate].
114pub struct NaiveDateAndStartYear {
115    /// The date to be converted.
116    pub date: NaiveDate,
117    /// The year at which the converted date will have started counting.
118    pub start_year: i32,
119}
120
121impl From<NaiveDateAndStartYear> for ArvelieDate {
122    fn from(value: NaiveDateAndStartYear) -> Self {
123        let NaiveDateAndStartYear { date, start_year } = value;
124        let year = date.year() - start_year;
125        let day_of_year = date.ordinal0() as i32;
126        let sub_year = match day_of_year {
127            365 => SubYear::LeapDay,
128            364 => SubYear::YearDay,
129            _ => {
130                let month = day_of_year / 14;
131                let day = day_of_year - month * 14;
132                SubYear::NormalSubYear(NormalSubYear {
133                    month: month as u8,
134                    day: day as u8,
135                })
136            }
137        };
138
139        ArvelieDate {
140            start_year,
141            year,
142            sub_year,
143        }
144    }
145}
146
147impl From<ArvelieDate> for NaiveDate {
148    fn from(value: ArvelieDate) -> Self {
149        NaiveDate::from_yo_opt(value.gregorian_year(), value.day_of_year() as u32).unwrap()
150    }
151}