doomsday/
doomsday.rs

1use std::fmt;
2use crate::*;
3
4///  Behold the 💥 doomsday of a year
5///
6/// Every year has a single **doomsday**, for example `Doomsday(2020)` is
7/// `Saturday`. Additionally, each month has fixed day number for which
8/// doomsday lies, for example, in `April` doomsday is always on the 4th. Given
9/// these two peices of information, it's possible to quickly determain the day
10/// of any date.
11///
12/// Years before `FIRST_LEAP_YEAR` aren't supported.
13#[derive(Debug)]
14pub struct Doomsday(pub usize);
15
16impl Doomsday {
17    /// Create a new doomsday for a given year.
18    /// TODO: Random or something?
19    #[inline]
20    pub fn new(year: usize) -> Doomsday {
21        Doomsday(year)
22    }
23
24    /// Return the **day number**, of the anchor doomsday in a given month.
25    ///
26    /// This is the main utility of this crate. Notice how there are really
27    /// only a few mnemonics to memorize here:
28    ///
29    /// - 4/4, 6/6, 8/8, 10/10, and 12/12
30    /// - 9 to 5 at 7-Eleven
31    /// - The last day of February (twice)
32    /// - 3 for three years, then 4 on the forth.
33    ///
34    /// These tricks are listed again below for each appropriate month.
35    //
36    // TODO: Formatting function. Would almost be trivial, if it wasn't for the 3rd.
37    pub fn anchor(&self, month: Month) -> usize {
38        match month {
39            // 3 for three years, then 4 on the forth.
40            January => if is_leap(self.0) { 4 } else { 3 },
41            // Always the last day of the month.
42            February => if is_leap(self.0) { 29 } else { 28 },
43            // Also the last day of February, interestingly.
44            March => 0,
45            // 4/4
46            April => 4,
47            // 9 to 5 (reversed)
48            May => 9,
49            // 6/6
50            June => 6,
51            // 7-Eleven
52            July => 11,
53            // 8/8
54            August => 8,
55            // 9 to 5
56            September => 5,
57            // 10/10
58            October => 10,
59            // 7-Eleven (reversed)
60            November => 7,
61            // 12/12
62            December => 12,
63        }
64    }
65
66    /// Return the **day of the week** which doomsday lies on in a given
67    /// year.
68    pub fn day(&self) -> Day {
69        Day::from_anchor(self.0 + leaps(self.0))
70    }
71}
72
73/// The display of doomsday is day, one for every week if you wish.
74impl fmt::Display for Doomsday {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        write!(f, "{}", self.day())
77    }
78}
79
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_day() {
87        assert_eq!(Day::Tuesday,  Doomsday(1752).day());
88        assert_eq!(Day::Sunday,   Doomsday(1993).day());
89        assert_eq!(Day::Saturday, Doomsday(2020).day());
90    }
91
92    #[test]
93    fn test_anchor() {
94        assert_eq!(3,  Doomsday(1993).anchor(Month::January));
95        assert_eq!(4,  Doomsday(2020).anchor(Month::January));
96        assert_eq!(28, Doomsday(1993).anchor(Month::February));
97        assert_eq!(29, Doomsday(2020).anchor(Month::February));
98        assert_eq!(0,  Doomsday(2000).anchor(Month::March));
99        assert_eq!(4,  Doomsday(2020).anchor(Month::April));
100        assert_eq!(9,  Doomsday(2020).anchor(Month::May));
101        assert_eq!(6,  Doomsday(2020).anchor(Month::June));
102        assert_eq!(11, Doomsday(2020).anchor(Month::July));
103        assert_eq!(8,  Doomsday(2020).anchor(Month::August));
104        assert_eq!(5,  Doomsday(2020).anchor(Month::September));
105        assert_eq!(10, Doomsday(2020).anchor(Month::October));
106        assert_eq!(7,  Doomsday(2020).anchor(Month::November));
107        assert_eq!(12, Doomsday(2020).anchor(Month::December));
108    }
109}