1mod utilities;
2
3use utilities::Template;
4
5pub struct Instant {
7 secs: u64,
8}
9
10impl Instant {
11 pub fn now() -> Self {
12 use std::time::SystemTime;
13
14 let time = SystemTime::now();
15 let since = time.duration_since(SystemTime::UNIX_EPOCH).unwrap();
16 let secs = since.as_secs();
17 Self { secs }
18 }
19
20 pub fn from_secs(secs: u64) -> Self {
21 Self { secs }
22 }
23
24 pub fn format(&self, template: &str) -> String {
26 let template = Template::new(template);
27 let secs = self.secs;
28
29 let offset_year = secs / NON_LEAP_YEAR;
30 let total_days = secs / DAY;
31 let mut day_of_year = total_days - offset_year * 365;
32
33 let leap_years = (BASE_YEAR..)
34 .take(offset_year as usize)
35 .filter(|year| is_leap_year(*year))
36 .count() as u64;
37 day_of_year -= leap_years;
38
39 let year = BASE_YEAR + offset_year;
40
41 let date_prefixes = if is_leap_year(year) {
42 LEAP_YEAR_MONTHS_PREFIX_SUM
43 } else {
44 NON_LEAP_YEAR_MONTHS_PREFIX_SUM
45 };
46
47 let (month, (month_name, day_sum)) = date_prefixes
48 .iter()
49 .enumerate()
50 .rev()
51 .find(|(_, (_, acc))| *acc < day_of_year)
52 .unwrap();
53
54 let date = day_of_year - day_sum + 1; let month = month + 1;
56
57 template.interpolate(|item| {
64 use std::borrow::Cow;
65
66 match item {
67 "second" => Cow::Owned(format!("{second:02}", second = secs % 60)),
68 "minute" => Cow::Owned(format!("{minute:02}", minute = (secs / MINUTE) % 60)),
69 "hour" | "hour24" => {
70 let hour = (secs / HOUR) % 24 + 1; Cow::Owned(format!("{hour:02}"))
72 }
73 "hour12" => {
74 let hour = (secs / HOUR) % 12 + 1; Cow::Owned(format!("{hour:02}"))
76 }
77 "week_day" => Cow::Borrowed(DAYS[(total_days as usize + 3) % 7]),
78 "date_suffix" => Cow::Borrowed(number_index_suffix(date as usize)),
79 "date" => Cow::Owned(format!("{date}")),
80 "month_name" => Cow::Borrowed(month_name),
81 "month" => Cow::Owned(format!("{month:02}")),
82 "year" => Cow::Owned(format!("{year}")),
83 name => {
84 panic!("unknown interpolation {name}");
85 }
86 }
87 })
88 }
89
90 pub fn seconds(&self) -> u64 {
91 self.secs
92 }
93
94 pub fn new(year: u64, month: u64, day: u64, hour: u64, minute: u64, second: u64) -> Self {
96 let date_prefixes = if is_leap_year(year) {
97 LEAP_YEAR_MONTHS_PREFIX_SUM
98 } else {
99 NON_LEAP_YEAR_MONTHS_PREFIX_SUM
100 };
101
102 let year = year - BASE_YEAR;
103
104 let leap_years = (BASE_YEAR..)
105 .take(year as usize)
106 .filter(|year| is_leap_year(*year))
107 .count() as u64;
108 let year = year * NON_LEAP_YEAR + leap_years * DAY;
109
110 let days = (day - 1 + date_prefixes[month as usize - 1].1) * DAY;
111 Self::from_secs(year + days + hour * HOUR + minute * MINUTE + second)
112 }
113
114 pub fn parse_english(on: &str) -> Result<Self, &str> {
116 let Some((date, rest)) = on.split_once(' ') else {
117 return Err(on);
118 };
119 let Some((month, year)) = rest.split_once(' ') else {
120 return Err(on);
121 };
122 let date = {
123 let suffixed = date.ends_with("st")
124 || date.ends_with("nd")
125 || date.ends_with("rd")
126 || date.ends_with("th");
127 if suffixed {
128 &date[..(date.len() - 2)]
129 } else {
130 date
131 }
132 };
133
134 let Ok(year) = year.parse() else {
135 return Err(on);
136 };
137
138 let months = if is_leap_year(year) {
139 LEAP_YEAR_MONTHS_PREFIX_SUM
140 } else {
141 NON_LEAP_YEAR_MONTHS_PREFIX_SUM
142 };
143
144 let month = if month.len() == 3 {
145 months
146 .iter()
147 .position(|(name, _)| name[..3].eq_ignore_ascii_case(month))
148 } else {
149 months
150 .iter()
151 .position(|(name, _)| name.eq_ignore_ascii_case(month))
152 };
153
154 let month = if let Some(month) = month {
155 month + 1
156 } else {
157 return Err(on);
158 };
159
160 let Ok(day) = date.parse() else {
161 return Err(on);
162 };
163
164 Ok(Self::new(year, month as u64, day, 12, 0, 0))
165 }
166
167 pub fn difference_days(&self, other: &Self) -> u64 {
168 (other.secs - self.secs) / DAY
170 }
171}
172
173pub const MINUTE: u64 = 60;
174pub const HOUR: u64 = 60 * MINUTE;
175pub const DAY: u64 = 24 * HOUR;
176pub const WEEK: u64 = 7 * DAY;
177
178#[allow(non_snake_case)]
179pub mod FORMATS {
180 pub const DATE_MONTH_YEAR: &str = "%date/%month/%year";
182 pub const MONTH_DATE_YEAR: &str = "%month/%date/%year";
184
185 pub const DATE_NAME_MONTH_YEAR: &str = "%week_day %date%date_suffix %month_name %year";
186 pub const TIME_DATE_NAME_MONTH_YEAR: &str =
187 "%hour:%minute %week_day %date%date_suffix %month_name %year";
188
189 pub const TIME: &str = "%hour:%minute";
190 pub const TIME_WITH_SECONDS: &str = "%hour:%minute:%second";
191
192 pub const FULL_MINIMAL: &str = "%hour:%minute:%second %date/%month/%year";
193}
194
195const NON_LEAP_YEAR: u64 = 365 * DAY;
196const BASE_YEAR: u64 = 1970;
199
200const NON_LEAP_YEAR_MONTHS_PREFIX_SUM: &[(&str, u64)] = &[
215 ("January", 0),
216 ("February", 31),
217 ("March", 59),
218 ("April", 90),
219 ("May", 120),
220 ("June", 151),
221 ("July", 181),
222 ("August", 212),
223 ("September", 243),
224 ("October", 273),
225 ("November", 304),
226 ("December", 334),
227];
228
229const LEAP_YEAR_MONTHS_PREFIX_SUM: &[(&str, u64)] = &[
230 ("January", 0),
231 ("February", 31),
232 ("March", 60),
233 ("April", 91),
234 ("May", 121),
235 ("June", 152),
236 ("July", 182),
237 ("August", 213),
238 ("September", 244),
239 ("October", 274),
240 ("November", 305),
241 ("December", 335),
242];
243
244const DAYS: &[&str] = &[
245 "Monday",
246 "Tuesday",
247 "Wednesday",
248 "Thursday",
249 "Friday",
250 "Saturday",
251 "Sunday",
252];
253
254fn is_leap_year(year: u64) -> bool {
255 year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
256}
257
258fn number_index_suffix(item: usize) -> &'static str {
259 match item % 10 {
260 1 => "st",
261 2 => "nd",
262 3 => "rd",
263 _ => "th",
264 }
265}