freedom_dates/
lib.rs

1use std::{error::Error, fmt::Display};
2
3pub use chrono::{DateTime as CommieDateTime, Duration, Utc as FancyTime};
4use dateparser::DateTimeUtc;
5
6const FREEDOM_FMT: &str = "%-m/%-d/%-y";
7//const FOUR_SCORE: u64 = 10_098_216_320; // seconds in 80 years
8
9/// Freedom was born at noon on the Fourth of July, '76, Eastern Time. This is History.
10pub const FREEDOMS_BIRTHDAY: &str = "1776-07-04T12:00:00-04:00";
11
12/// A Result of Freedom.
13pub type Freesult = Result<FreedomDate, FreedomError>;
14
15/// Either your date string makes no sense because it's too Communist, or because it refers to some
16/// impossible date that is ... "before" the start of Time/Freedom itself.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum FreedomError {
19    TooCommunist(String),
20    PreCreation(String),
21}
22
23impl Display for FreedomError {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match self {
26            FreedomError::TooCommunist(s) => {
27                write!(f, "I don't speak your crazy Communism-language! '{s}'")
28            }
29            FreedomError::PreCreation(s) => write!(
30                f,
31                "That doesn't hardly make no sense, '{s}' is before the very start of Time/Freedom itself.",
32            ),
33        }
34    }
35}
36
37impl Error for FreedomError {}
38
39/// FreedomTime is aware of the Birthday of Freedom (July 4, '76).
40pub trait FreedomTime {
41    /// Number of whole seconds since the Birthday of Freedom.
42    fn freedomstamp(&self) -> u64;
43}
44
45/// A FreedomDate is the One True Date representation. All other models are Communist.
46#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
47pub struct FreedomDate {
48    shot_heard_around_the_world: CommieDateTime<FancyTime>,
49    shot_heard_around_the_universe: CommieDateTime<FancyTime>,
50}
51
52/// By default, FreedomDates are July 4th, '76.
53impl Default for FreedomDate {
54    fn default() -> Self {
55        Self {
56            shot_heard_around_the_world: CommieDateTime::parse_from_rfc3339(FREEDOMS_BIRTHDAY)
57                .unwrap()
58                .into(),
59            shot_heard_around_the_universe: CommieDateTime::parse_from_rfc3339(FREEDOMS_BIRTHDAY)
60                .unwrap()
61                .into(),
62        }
63    }
64}
65
66impl Display for FreedomDate {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        write!(
69            f,
70            "{}",
71            self.shot_heard_around_the_world.format(FREEDOM_FMT)
72        )
73    }
74}
75
76impl std::ops::Add<Duration> for FreedomDate {
77    type Output = Self;
78
79    fn add(self, rhs: Duration) -> Self::Output {
80        Self {
81            shot_heard_around_the_world: self.shot_heard_around_the_world + rhs,
82            shot_heard_around_the_universe: self.shot_heard_around_the_universe,
83        }
84    }
85}
86
87impl std::ops::Sub<Duration> for FreedomDate {
88    type Output = Freesult;
89
90    fn sub(self, rhs: Duration) -> Self::Output {
91        let datestring = self.shot_heard_around_the_world - rhs;
92        let datestring = format!("{}", datestring.format(FREEDOM_FMT));
93        FreedomDate::new(
94            self.shot_heard_around_the_world - rhs,
95            self.shot_heard_around_the_universe,
96            &datestring,
97        )
98    }
99}
100
101impl std::ops::Sub<Self> for FreedomDate {
102    type Output = Duration;
103
104    fn sub(self, rhs: Self) -> Self::Output {
105        self.shot_heard_around_the_world - rhs.shot_heard_around_the_world
106    }
107}
108
109impl FreedomTime for FreedomDate {
110    fn freedomstamp(&self) -> u64 {
111        (self.shot_heard_around_the_world - self.shot_heard_around_the_universe).num_seconds()
112            as u64
113    }
114}
115
116/// A FreedomDate that is `value` seconds after the Birthday of Freedom.
117impl From<u64> for FreedomDate {
118    fn from(value: u64) -> Self {
119        let f = FreedomDate::default();
120        let dur = Duration::seconds(value as i64);
121        f + dur
122    }
123}
124
125impl FreedomDate {
126    /// To liberate a representation of a date is to make it Free. But the FreeDate tree must
127    /// occasionally be watered with the blood of badly-formed datestrings, and here is where the
128    /// true test of Datriots is found.
129    pub fn liberate(datestring: &str) -> Freesult {
130        let bang = if let Ok(bang) = datestring.parse::<DateTimeUtc>() {
131            bang
132        } else {
133            return Err(FreedomError::TooCommunist(datestring.to_owned()));
134        };
135        let big_bang = FREEDOMS_BIRTHDAY.parse::<DateTimeUtc>().unwrap();
136
137        FreedomDate::new(bang.0, big_bang.0, datestring)
138    }
139
140    fn new(
141        bang: CommieDateTime<FancyTime>,
142        big_bang: CommieDateTime<FancyTime>,
143        datestring: &str,
144    ) -> Freesult {
145        if bang < big_bang {
146            Err(FreedomError::PreCreation(datestring.to_owned()))
147        } else {
148            Ok(FreedomDate {
149                shot_heard_around_the_world: bang,
150                shot_heard_around_the_universe: big_bang,
151            })
152        }
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn default_is_birthday() {
162        let result = format!("{}", FreedomDate::default());
163        assert_eq!(result, "7/4/76");
164    }
165
166    #[test]
167    fn no_time_before_time() {
168        let result = FreedomDate::liberate("1774-01-01");
169        assert!(match result {
170            Err(FreedomError::PreCreation(_)) => true,
171            _ => false,
172        });
173    }
174
175    #[test]
176    fn i_dont_speak_your_crazy_moon_language() {
177        let result = FreedomDate::liberate("this is not a datestring of honor");
178        assert!(match result {
179            Err(FreedomError::TooCommunist(_)) => true,
180            _ => false,
181        });
182    }
183
184    #[test]
185    fn a_regular_date_that_is_fine() {
186        let result = FreedomDate::liberate("2023-02-07T12:00:00-07:00").unwrap();
187        assert_eq!("2/7/23", &result.to_string());
188    }
189
190    #[test]
191    fn cant_trick_me_with_subtraction_wizard() {
192        let result = FreedomDate::default() - chrono::Duration::days(2);
193        assert!(match result {
194            Err(FreedomError::PreCreation(_)) => true,
195            _ => false,
196        });
197    }
198
199    #[test]
200    fn one_day_after_americas_birthday() {
201        let hangover = FreedomDate::default() + chrono::Duration::days(1);
202        let seconds = 24 * 60 * 60;
203        assert_eq!(hangover.freedomstamp(), seconds);
204    }
205}