1pub trait Clock: Send + Sync {
12 fn strftime(&self, format: &str) -> String;
14}
15
16#[derive(Clone, Copy, Debug)]
18pub(crate) struct Civil {
19 pub year: i64,
20 pub month: u32, pub day: u32, pub hour: u32,
23 pub min: u32,
24 pub sec: u32,
25 pub weekday: u32, pub yday: u32, }
28
29const MONTHS_FULL: [&str; 12] = [
30 "January",
31 "February",
32 "March",
33 "April",
34 "May",
35 "June",
36 "July",
37 "August",
38 "September",
39 "October",
40 "November",
41 "December",
42];
43const MONTHS_ABBR: [&str; 12] = [
44 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
45];
46const DAYS_FULL: [&str; 7] = [
47 "Sunday",
48 "Monday",
49 "Tuesday",
50 "Wednesday",
51 "Thursday",
52 "Friday",
53 "Saturday",
54];
55const DAYS_ABBR: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
56
57pub(crate) fn civil_from_days(z: i64) -> (i64, u32, u32) {
60 let z = z + 719_468;
61 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
62 let doe = z - era * 146_097; let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365; let y = yoe + era * 400;
65 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = (doy - (153 * mp + 2) / 5 + 1) as u32; let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32; let year = if m <= 2 { y + 1 } else { y };
70 (year, m, d)
71}
72
73fn is_leap(year: i64) -> bool {
75 (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
76}
77
78fn day_of_year(year: i64, month: u32, day: u32) -> u32 {
80 const CUM: [u32; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
81 let mut d = CUM[(month - 1) as usize] + day;
82 if month > 2 && is_leap(year) {
83 d += 1;
84 }
85 d
86}
87
88impl Civil {
89 pub(crate) fn from_unix_secs(secs: i64) -> Civil {
91 let days = secs.div_euclid(86_400);
92 let tod = secs.rem_euclid(86_400);
93 let (year, month, day) = civil_from_days(days);
94 let weekday = ((days.rem_euclid(7) + 4) % 7) as u32;
96 Civil {
97 year,
98 month,
99 day,
100 hour: (tod / 3600) as u32,
101 min: ((tod % 3600) / 60) as u32,
102 sec: (tod % 60) as u32,
103 weekday,
104 yday: day_of_year(year, month, day),
105 }
106 }
107
108 pub(crate) fn strftime(&self, format: &str) -> String {
110 let mut out = String::with_capacity(format.len() + 8);
111 let mut chars = format.chars().peekable();
112 while let Some(c) = chars.next() {
113 if c != '%' {
114 out.push(c);
115 continue;
116 }
117 match chars.next() {
118 Some('Y') => out.push_str(&self.year.to_string()),
119 Some('y') => out.push_str(&format!("{:02}", self.year.rem_euclid(100))),
120 Some('m') => out.push_str(&format!("{:02}", self.month)),
121 Some('d') => out.push_str(&format!("{:02}", self.day)),
122 Some('e') => out.push_str(&format!("{:2}", self.day)),
123 Some('B') => out.push_str(MONTHS_FULL[(self.month - 1) as usize]),
124 Some('b') | Some('h') => out.push_str(MONTHS_ABBR[(self.month - 1) as usize]),
125 Some('A') => out.push_str(DAYS_FULL[self.weekday as usize]),
126 Some('a') => out.push_str(DAYS_ABBR[self.weekday as usize]),
127 Some('j') => out.push_str(&format!("{:03}", self.yday)),
128 Some('H') => out.push_str(&format!("{:02}", self.hour)),
129 Some('I') => {
130 let h12 = match self.hour % 12 {
131 0 => 12,
132 h => h,
133 };
134 out.push_str(&format!("{:02}", h12));
135 }
136 Some('M') => out.push_str(&format!("{:02}", self.min)),
137 Some('S') => out.push_str(&format!("{:02}", self.sec)),
138 Some('p') => out.push_str(if self.hour < 12 { "AM" } else { "PM" }),
139 Some('%') => out.push('%'),
140 Some(other) => {
142 out.push('%');
143 out.push(other);
144 }
145 None => out.push('%'),
146 }
147 }
148 out
149 }
150}
151
152#[derive(Clone, Copy, Debug, Default)]
158pub struct SystemClock;
159
160impl Clock for SystemClock {
161 fn strftime(&self, format: &str) -> String {
162 let secs = std::time::SystemTime::now()
163 .duration_since(std::time::UNIX_EPOCH)
164 .map(|d| d.as_secs() as i64)
165 .unwrap_or(0);
166 Civil::from_unix_secs(secs).strftime(format)
167 }
168}
169
170#[derive(Clone, Copy, Debug)]
172pub struct FixedClock {
173 unix_secs: i64,
174}
175
176impl FixedClock {
177 pub fn from_unix_secs(unix_secs: i64) -> Self {
179 FixedClock { unix_secs }
180 }
181
182 pub fn from_ymd(year: i64, month: u32, day: u32) -> Option<Self> {
185 if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
186 return None;
187 }
188 let y = if month <= 2 { year - 1 } else { year };
190 let era = if y >= 0 { y } else { y - 399 } / 400;
191 let yoe = y - era * 400;
192 let mp = if month > 2 { month - 3 } else { month + 9 } as i64;
193 let doy = (153 * mp + 2) / 5 + day as i64 - 1;
194 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
195 let days = era * 146_097 + doe - 719_468;
196 Some(FixedClock {
197 unix_secs: days * 86_400,
198 })
199 }
200}
201
202impl Clock for FixedClock {
203 fn strftime(&self, format: &str) -> String {
204 Civil::from_unix_secs(self.unix_secs).strftime(format)
205 }
206}