1mod utilities;
2
3use utilities::Template;
4
5pub struct Instant {
7 utc_secs: u64,
9 utc_offset: i32,
10}
11
12impl Instant {
13 pub fn now_utc() -> Self {
14 use std::time::SystemTime;
15
16 let time = SystemTime::now();
17 let since = time.duration_since(SystemTime::UNIX_EPOCH).unwrap();
18 let utc_secs = since.as_secs();
19 Self::from_utc_secs(utc_secs)
20 }
21
22 pub fn now_local() -> Self {
23 use std::time::SystemTime;
24
25 let time = SystemTime::now();
26 let since = time.duration_since(SystemTime::UNIX_EPOCH).unwrap();
27 let utc_secs = since.as_secs();
28 let utc_offset = system_utc_offset_seconds();
29 Self {
30 utc_secs,
31 utc_offset,
32 }
33 }
34
35 pub fn from_utc_secs(utc_secs: u64) -> Self {
36 Self {
37 utc_secs,
38 utc_offset: 0,
39 }
40 }
41
42 pub fn from_system_time(time: std::time::SystemTime) -> Self {
44 let secs = time
45 .duration_since(std::time::SystemTime::UNIX_EPOCH)
46 .unwrap()
47 .as_secs();
48 Self::from_utc_secs(secs)
49 }
50
51 pub fn format(&self, template: &str) -> String {
52 let template = Template::new(template, '%');
53 let secs = self.seconds();
54
55 let day_of_week_str = self.day_of_week_str();
56
57 let (day_of_year, offset_year) = self.day_of_year_and_offset_year();
58
59 let year = BASE_YEAR + offset_year;
60
61 let date_prefixes = if is_leap_year(year) {
62 LEAP_YEAR_MONTHS_PREFIX_SUM
63 } else {
64 NON_LEAP_YEAR_MONTHS_PREFIX_SUM
65 };
66
67 let result = date_prefixes
68 .iter()
69 .enumerate()
70 .rev()
71 .find(|(_, (_, _, acc))| *acc <= day_of_year);
72
73 let Some((month, (month_name, _, day_sum))) = result else {
74 panic!("bad day of year {day_of_year}");
75 };
76
77 let date = day_of_year - day_sum + 1; let month = month + 1;
79
80 let week = self.week_of_year();
81
82 template.interpolate(|item| {
89 use std::borrow::Cow;
90
91 fn hour_zero_to_twelve(i: u64) -> u64 {
92 if i == 0 { 12 } else { i }
93 }
94
95 match item {
96 "second" => Cow::Owned(format!("{second:02}", second = secs % 60)),
97 "minute" => Cow::Owned(format!("{minute:02}", minute = (secs / MINUTE) % 60)),
98 "hour" | "hour24" => Cow::Owned(format!("{hour:02}", hour = (secs / HOUR) % 24)),
99 "hour12" => Cow::Owned(format!(
100 "{hour:02}",
101 hour = hour_zero_to_twelve((secs / HOUR) % 12)
102 )),
103 "week" => Cow::Owned(week.to_string()),
105 "week_day" => Cow::Borrowed(day_of_week_str),
106 "week_day_short" => Cow::Borrowed(&day_of_week_str[..3]),
107 "date_suffix" => Cow::Borrowed(number_index_suffix(date as usize)),
108 "date" => Cow::Owned(format!("{date:02}")),
109 "month_name" => Cow::Borrowed(month_name),
110 "month_name_short" => Cow::Borrowed(&month_name[..3]),
111 "month" => Cow::Owned(format!("{month:02}")),
112 "full_year" => Cow::Owned(format!("{year}", year = year % 100)),
113 "year" => Cow::Owned(format!("{year}")),
114 "offset" => {
115 fn offset(prefix: char, utc_offset: u64) -> String {
117 let hour = utc_offset / HOUR;
118 let minute = (utc_offset / MINUTE) % 60;
119 format!("{prefix}{hour:02}{minute:02}")
120 }
121
122 if self.utc_offset < 0 {
123 Cow::Owned(offset('-', -self.utc_offset as u64))
124 } else {
125 Cow::Owned(offset('+', self.utc_offset as u64))
126 }
127 }
128 name => {
129 panic!("unknown interpolation {name}");
130 }
131 }
132 })
133 }
134
135 pub fn seconds(&self) -> u64 {
137 if self.utc_offset > 0 {
139 self.utc_secs + self.utc_offset as u64
140 } else {
141 self.utc_secs - self.utc_offset as u64
142 }
143 }
144
145 pub fn new(year: u64, month: u64, day: u64, hour: u64, minute: u64, second: u64) -> Self {
147 let date_prefixes = if is_leap_year(year) {
148 LEAP_YEAR_MONTHS_PREFIX_SUM
149 } else {
150 NON_LEAP_YEAR_MONTHS_PREFIX_SUM
151 };
152
153 let year = year - BASE_YEAR;
154
155 let leap_years = (BASE_YEAR..)
156 .take(year as usize)
157 .filter(|year| is_leap_year(*year))
158 .count() as u64;
159 let year = year * NON_LEAP_YEAR + leap_years * DAY;
160
161 let days = (day - 1 + date_prefixes[month as usize - 1].2) * DAY;
162 Self::from_utc_secs(year + days + hour * HOUR + minute * MINUTE + second)
163 }
164
165 pub fn parse_english<S: AsRef<str>>(
168 mut words: impl Iterator<Item = S>,
169 ) -> Result<Self, &'static str> {
170 let Some(mut date) = words.next() else {
171 return Err("no date");
172 };
173
174 if let Some((day, month_and_year)) = date.as_ref().split_once('/') {
175 let (month, year) = month_and_year.split_once('/').unwrap();
176
177 let day = str::parse::<u64>(day).unwrap();
178 let month = str::parse::<u64>(month).unwrap();
179 let year = str::parse::<u64>(year).unwrap();
180
181 let year = if year < 100 { year + 2000 } else { year };
182
183 let next = words.next();
184 let (hour, minute, second) = if let Some(next) = next
185 && let Some(triple) = parse_time(next.as_ref())
186 {
187 triple
188 } else {
189 (12, 0, 0)
190 };
191
192 Ok(Instant::new(year, month, day, hour, minute, second))
193 } else if let Some((hour, minute, second)) = parse_time(date.as_ref()) {
194 Ok(Instant::now_local().set_time(hour, minute, second))
195 } else {
196 if date.as_ref().eq_ignore_ascii_case("the") {
197 if let Some(next) = words.next() {
198 date = next;
199 } else {
200 return Err("nothing after the");
201 }
202 }
203
204 let Some(mut month) = words.next() else {
205 return Err("no month");
206 };
207
208 if month.as_ref().eq_ignore_ascii_case("of") {
209 if let Some(next) = words.next() {
210 month = next;
211 } else {
212 return Err("nothing after of");
213 }
214 }
215
216 let year = words.next();
217
218 let year = if let Some(year) = year {
221 let Ok(year) = year.as_ref().parse() else {
222 return Err("invalid year");
223 };
224 if year < 100 { year + 2000 } else { year }
225 } else {
226 Instant::now_local().year()
227 };
228
229 let month = month.as_ref();
232
233 let months = if is_leap_year(year) {
234 LEAP_YEAR_MONTHS_PREFIX_SUM
235 } else {
236 NON_LEAP_YEAR_MONTHS_PREFIX_SUM
237 };
238
239 let month = if month.len() == 3 {
240 months
241 .iter()
242 .position(|(name, ..)| name[..3].eq_ignore_ascii_case(month))
243 } else {
244 months
245 .iter()
246 .position(|(name, ..)| name.eq_ignore_ascii_case(month))
247 };
248
249 let month = if let Some(month) = month {
250 month + 1
251 } else {
252 return Err("cannot find month");
253 };
254
255 let date = {
257 let date = date.as_ref();
258 let suffixed = date.ends_with("st")
259 || date.ends_with("nd")
260 || date.ends_with("rd")
261 || date.ends_with("th");
262 if suffixed {
263 &date[..(date.len() - 2)]
264 } else {
265 date
266 }
267 };
268
269 let Ok(day) = date.parse() else {
270 return Err("invalid date");
271 };
272
273 let (hour, minute, seconds) = if let Some(other) = words.next() {
274 if let Some(triple) = parse_time(other.as_ref()) {
275 triple
276 } else {
277 return Err("invalid time");
278 }
279 } else {
280 (12, 0, 0)
281 };
282
283 Ok(Self::new(year, month as u64, day, hour, minute, seconds))
284 }
285 }
286
287 pub fn difference(&self, other: Instant) -> Result<Duration, Duration> {
289 let difference = self.seconds().checked_sub(other.seconds());
290 match difference {
291 Some(secs) => Ok(Duration { secs }),
292 None => Err(Duration {
293 secs: other.seconds() - self.seconds(),
294 }),
295 }
296 }
297
298 #[must_use]
299 pub fn set_time(mut self, hours: u64, minutes: u64, seconds: u64) -> Self {
300 self.utc_secs -= self.utc_secs % DAY;
302 self.utc_secs -= self.utc_secs % HOUR;
303 self.utc_secs -= self.utc_secs % MINUTE;
304
305 self.utc_secs += hours * HOUR;
307 self.utc_secs += minutes * MINUTE;
308 self.utc_secs += seconds;
309 self
310 }
311
312 #[must_use]
313 pub fn year(&self) -> u64 {
314 let non_leap_years = self.seconds() / NON_LEAP_YEAR;
316 let accounting_for_leap_years =
317 (self.seconds() - DAY * leap_years_since(non_leap_years)) / NON_LEAP_YEAR;
318 BASE_YEAR + accounting_for_leap_years
319 }
320
321 #[must_use]
322 pub fn day_of_week(&self) -> u8 {
323 ((self.seconds() / DAY + 3) % 7) as u8
324 }
325
326 #[must_use]
327 pub fn day_of_week_str(&self) -> &str {
328 DAYS[self.day_of_week() as usize]
329 }
330
331 #[must_use]
333 pub fn week_of_year(&self) -> u64 {
334 let (day_of_year, _) = self.day_of_year_and_offset_year();
335 let offset = Self::utc_start_of_year(self.year()).day_of_week() as u64;
336 ((day_of_year + offset) / 7) + 1
337 }
338
339 fn day_of_year_and_offset_year(&self) -> (u64, u64) {
340 let secs = self.seconds();
341
342 let offset_year = secs / NON_LEAP_YEAR;
343 let day_of_year = (secs / DAY) % 365;
344
345 let leap_years = leap_years_since(offset_year);
346
347 if leap_years > day_of_year {
348 let year = offset_year - 1;
349 let day = (count_of_days_in_year(BASE_YEAR + year) + day_of_year) - leap_years;
350 (day, year)
351 } else {
352 (day_of_year - leap_years, offset_year)
353 }
354 }
355
356 pub fn month(&self) -> (&'static str, u64, u64) {
357 let (day_of_year, offset_year) = self.day_of_year_and_offset_year();
358
359 let year = BASE_YEAR + offset_year;
360 let date_prefixes = if is_leap_year(year) {
361 LEAP_YEAR_MONTHS_PREFIX_SUM
362 } else {
363 NON_LEAP_YEAR_MONTHS_PREFIX_SUM
364 };
365
366 let result = date_prefixes
367 .iter()
368 .enumerate()
369 .rev()
370 .find(|(_, (_, _, acc))| *acc <= day_of_year);
371
372 let Some((month, (month_name, _, day_sum))) = result else {
373 panic!("bad day of year {day_of_year}");
374 };
375
376 let date = day_of_year - day_sum + 1; (month_name, month as u64, date)
378 }
379
380 #[must_use]
384 pub fn utc_start_of_year(year: u64) -> Self {
385 let year = year - BASE_YEAR;
386 Self {
387 utc_secs: year * NON_LEAP_YEAR + leap_years_since(year) * DAY,
388 utc_offset: 0,
389 }
390 }
391}
392
393fn parse_time(on: &str) -> Option<(u64, u64, u64)> {
394 if let Some((hour, minute_and_seconds)) = on.split_once(':') {
395 let hour = str::parse::<u64>(hour).unwrap();
396 if let Some((minute, second)) = minute_and_seconds.split_once(':') {
397 let minute = str::parse::<u64>(minute).unwrap();
398 let second = str::parse::<u64>(second).unwrap();
399 Some((hour, minute, second))
400 } else {
401 let minute = minute_and_seconds;
402 let minute = str::parse::<u64>(minute).unwrap();
403 Some((hour, minute, 0))
404 }
405 } else {
406 None
407 }
408}
409
410pub struct Month {
411 pub name: &'static str,
412 pub days: u8,
413}
414
415pub struct Year(u32);
417
418impl Year {
419 pub fn new(year: u32) -> Self {
420 Self(year)
421 }
422 pub fn months(&self) -> impl Iterator<Item = Month> {
423 let months = if is_leap_year(self.0 as u64) {
424 LEAP_YEAR_MONTHS_PREFIX_SUM
425 } else {
426 NON_LEAP_YEAR_MONTHS_PREFIX_SUM
427 };
428
429 months.iter().map(|(name, days, _)| Month {
430 name,
431 days: *days as u8,
432 })
433 }
434}
435
436#[derive(Clone, Copy)]
437pub struct Duration {
438 pub(crate) secs: u64,
439}
440
441impl Duration {
442 #[must_use]
443 pub const fn from_secs(secs: u64) -> Self {
444 Self { secs }
445 }
446
447 #[must_use]
448 pub const fn from_seconds(secs: u64) -> Self {
449 Self { secs }
450 }
451
452 #[must_use]
453 pub const fn from_minutes(minutes: u64) -> Self {
454 Self {
455 secs: minutes * MINUTE,
456 }
457 }
458
459 #[must_use]
460 pub const fn from_hours(hours: u64) -> Self {
461 Self { secs: hours * HOUR }
462 }
463
464 #[must_use]
465 pub const fn from_days(days: u64) -> Self {
466 Self { secs: days * DAY }
467 }
468
469 #[must_use]
470 pub const fn from_weeks(weeks: u64) -> Self {
471 Self { secs: weeks * WEEK }
472 }
473
474 #[must_use]
475 pub fn format(&self) -> String {
476 fn format_(secs: u64, precision: usize, buf: &mut String) {
477 use std::fmt::Write;
478
479 if secs == 0 {
480 return;
481 }
482
483 if !buf.is_empty() {
484 if precision > 1 {
485 buf.push_str(", ");
486 } else if precision == 1 {
487 buf.push_str(" and ");
488 } else if precision == 0 {
489 return;
490 }
491 }
492
493 if secs < MINUTE {
494 write!(buf, "{secs} seconds").unwrap();
495 } else if secs < HOUR {
496 let mins = secs / MINUTE;
497 write!(buf, "{mins} minutes").unwrap();
498 format_(secs % MINUTE, precision - 1, buf);
499 } else if secs < DAY {
500 let hours = secs / HOUR;
501 write!(buf, "{hours} hours").unwrap();
502 format_(secs % HOUR, precision - 1, buf);
503 } else if secs < WEEK {
504 let days = secs / DAY;
505 write!(buf, "{days} days").unwrap();
506 format_(secs % DAY, precision - 1, buf);
507 } else if secs < NON_LEAP_YEAR {
508 let weeks = secs / WEEK;
509 write!(buf, "{weeks} weeks").unwrap();
510 format_(secs % WEEK, precision - 1, buf);
511 } else {
512 const MONTH: u64 = DAY * 30;
513 let months = secs / MONTH;
514 write!(buf, "{months} months").unwrap();
515 }
517 }
518
519 let precision = 2;
520 let mut buf = String::new();
521
522 format_(self.secs, precision, &mut buf);
523
524 buf
525 }
526}
527
528impl std::ops::Add<Duration> for Instant {
529 type Output = Self;
530
531 fn add(mut self, duration: Duration) -> Self::Output {
532 self.utc_secs += duration.secs;
533 self
534 }
535}
536
537impl std::ops::AddAssign<Duration> for Instant {
538 fn add_assign(&mut self, duration: Duration) {
539 self.utc_secs += duration.secs;
540 }
541}
542
543impl std::ops::Sub<Duration> for Instant {
544 type Output = Self;
545
546 fn sub(mut self, duration: Duration) -> Self::Output {
547 self.utc_secs -= duration.secs;
548 self
549 }
550}
551
552impl std::ops::SubAssign<Duration> for Instant {
553 fn sub_assign(&mut self, duration: Duration) {
554 self.utc_secs -= duration.secs;
555 }
556}
557
558pub const MINUTE: u64 = 60;
559pub const HOUR: u64 = 60 * MINUTE;
560pub const DAY: u64 = 24 * HOUR;
561pub const WEEK: u64 = 7 * DAY;
562
563const NON_LEAP_YEAR: u64 = 365 * DAY;
564const BASE_YEAR: u64 = 1970;
567
568const NON_LEAP_YEAR_MONTHS_PREFIX_SUM: &[(&str, u64, u64)] = &[
583 ("January", 31, 0),
584 ("February", 28, 31),
585 ("March", 31, 59),
586 ("April", 30, 90),
587 ("May", 31, 120),
588 ("June", 30, 151),
589 ("July", 31, 181),
590 ("August", 31, 212),
591 ("September", 30, 243),
592 ("October", 31, 273),
593 ("November", 30, 304),
594 ("December", 31, 334),
595];
596
597const LEAP_YEAR_MONTHS_PREFIX_SUM: &[(&str, u64, u64)] = &[
598 ("January", 31, 0),
599 ("February", 29, 31),
600 ("March", 31, 60),
601 ("April", 30, 91),
602 ("May", 31, 121),
603 ("June", 30, 152),
604 ("July", 31, 182),
605 ("August", 31, 213),
606 ("September", 30, 244),
607 ("October", 31, 274),
608 ("November", 30, 305),
609 ("December", 31, 335),
610];
611
612const DAYS: &[&str] = &[
613 "Monday",
614 "Tuesday",
615 "Wednesday",
616 "Thursday",
617 "Friday",
618 "Saturday",
619 "Sunday",
620];
621
622pub fn count_of_days_in_year(year: u64) -> u64 {
623 365 + is_leap_year(year) as u64
624}
625
626fn is_leap_year(year: u64) -> bool {
627 year.is_multiple_of(4) && (!year.is_multiple_of(100) || year.is_multiple_of(400))
628}
629
630fn leap_years_since(offset_year: u64) -> u64 {
631 (BASE_YEAR..)
632 .take(offset_year as usize)
633 .filter(|year| is_leap_year(*year))
634 .count() as u64
635}
636
637fn number_index_suffix(item: usize) -> &'static str {
638 match item % 10 {
639 1 => "st",
640 2 => "nd",
641 3 => "rd",
642 _ => "th",
643 }
644}
645
646#[allow(non_snake_case)]
647pub mod FORMATS {
648 pub const DATE_MONTH_YEAR: &str = "%date/%month/%year";
650 pub const MONTH_DATE_YEAR: &str = "%month/%date/%year";
652
653 pub const DATE_NAME_MONTH_YEAR: &str = "%week_day %date%date_suffix %month_name %year";
654 pub const TIME_DATE_NAME_MONTH_YEAR: &str =
655 "%hour:%minute %week_day %date%date_suffix %month_name %year";
656 pub const ENGLISH: &str = "%week_day the %date%date_suffix of %month_name %year";
657
658 pub const TIME: &str = "%hour:%minute";
659 pub const TIME_WITH_SECONDS: &str = "%hour:%minute:%second";
660
661 pub const WEEK: &str = "%week";
662
663 pub const FULL_MINIMAL: &str = "%hour:%minute:%second %date/%month/%year";
664
665 pub const FULL: &str = "%hour:%minute:%second %date/%month/%year";
667
668 pub const ISO_8601: &str = "%year-%month-%dateT%hour:%minute:%second";
669 pub const RFC_2822: &str = "%year-%month-%dateT%hour:%minute:%second";
671}
672
673#[cfg(unix)]
674fn system_utc_offset_seconds() -> i32 {
675 use std::mem::MaybeUninit;
676
677 unsafe {
678 let mut now: libc::time_t = 0;
679 libc::time(&raw mut now);
680
681 let mut tm = MaybeUninit::<libc::tm>::uninit();
682 libc::localtime_r(&raw const now, tm.as_mut_ptr());
683
684 let tm = tm.assume_init();
685 i32::try_from(tm.tm_gmtoff).unwrap()
686 }
687}
688
689#[cfg(windows)]
690fn system_utc_offset_seconds() -> i32 {
691 use std::mem::MaybeUninit;
692 use winapi::um::timezoneapi::GetTimeZoneInformation;
693 use winapi::um::timezoneapi::TIME_ZONE_INFORMATION;
694
695 unsafe {
696 let mut tzi = MaybeUninit::<TIME_ZONE_INFORMATION>::uninit();
697 let state = GetTimeZoneInformation(tzi.as_mut_ptr());
698 let tzi = tzi.assume_init();
699
700 let mut bias = tzi.Bias;
701
702 match state {
703 winapi::um::timezoneapi::TIME_ZONE_ID_DAYLIGHT => {
704 bias += tzi.DaylightBias;
705 }
706 winapi::um::timezoneapi::TIME_ZONE_ID_STANDARD => {
707 bias += tzi.StandardBias;
708 }
709 _ => {}
710 }
711
712 -bias * 60
714 }
715}