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";
7pub const FREEDOMS_BIRTHDAY: &str = "1776-07-04T12:00:00-04:00";
11
12pub type Freesult = Result<FreedomDate, FreedomError>;
14
15#[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
39pub trait FreedomTime {
41 fn freedomstamp(&self) -> u64;
43}
44
45#[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
52impl 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
116impl 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 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}