1use std::borrow::Cow;
2use std::cmp::max;
3use std::fmt;
4use std::time::SystemTime;
5
6use chrono::{DateTime, Duration, TimeZone, Utc};
7
8use crate::Humanize;
9
10#[derive(Clone, Copy, Debug, Hash, PartialEq, PartialOrd)]
12pub enum Tense {
13 Past,
14 Present,
15 Future,
16}
17
18#[derive(Clone, Copy, Debug, Hash, PartialEq, PartialOrd)]
20pub enum Accuracy {
21 Rough,
23 Precise,
25}
26
27impl Accuracy {
28 #[must_use]
30 pub fn is_precise(self) -> bool {
31 self == Self::Precise
32 }
33
34 #[must_use]
36 pub fn is_rough(self) -> bool {
37 self == Self::Rough
38 }
39}
40
41const S_MINUTE: i64 = 60;
43const S_HOUR: i64 = S_MINUTE * 60;
44const S_DAY: i64 = S_HOUR * 24;
45const S_WEEK: i64 = S_DAY * 7;
46const S_MONTH: i64 = S_DAY * 30;
47const S_YEAR: i64 = S_DAY * 365;
48
49#[derive(Clone, Copy, Debug)]
50enum TimePeriod {
51 Now,
52 Nanos(i64),
53 Micros(i64),
54 Millis(i64),
55 Seconds(i64),
56 Minutes(i64),
57 Hours(i64),
58 Days(i64),
59 Weeks(i64),
60 Months(i64),
61 Years(i64),
62 Eternity,
63}
64
65impl TimePeriod {
66 fn to_text_precise(self) -> Cow<'static, str> {
67 match self {
68 Self::Now => "now".into(),
69 Self::Nanos(n) => format!("{} ns", n).into(),
70 Self::Micros(n) => format!("{} µs", n).into(),
71 Self::Millis(n) => format!("{} ms", n).into(),
72 Self::Seconds(1) => "1 second".into(),
73 Self::Seconds(n) => format!("{} seconds", n).into(),
74 Self::Minutes(1) => "1 minute".into(),
75 Self::Minutes(n) => format!("{} minutes", n).into(),
76 Self::Hours(1) => "1 hour".into(),
77 Self::Hours(n) => format!("{} hours", n).into(),
78 Self::Days(1) => "1 day".into(),
79 Self::Days(n) => format!("{} days", n).into(),
80 Self::Weeks(1) => "1 week".into(),
81 Self::Weeks(n) => format!("{} weeks", n).into(),
82 Self::Months(1) => "1 month".into(),
83 Self::Months(n) => format!("{} months", n).into(),
84 Self::Years(1) => "1 year".into(),
85 Self::Years(n) => format!("{} years", n).into(),
86 Self::Eternity => "eternity".into(),
87 }
88 }
89
90 fn to_text_rough(self) -> Cow<'static, str> {
91 match self {
92 Self::Now => "now".into(),
93 Self::Nanos(n) => format!("{} ns", n).into(),
94 Self::Micros(n) => format!("{} µs", n).into(),
95 Self::Millis(n) => format!("{} ms", n).into(),
96 Self::Seconds(n) => format!("{} seconds", n).into(),
97 Self::Minutes(1) => "a minute".into(),
98 Self::Minutes(n) => format!("{} minutes", n).into(),
99 Self::Hours(1) => "an hour".into(),
100 Self::Hours(n) => format!("{} hours", n).into(),
101 Self::Days(1) => "a day".into(),
102 Self::Days(n) => format!("{} days", n).into(),
103 Self::Weeks(1) => "a week".into(),
104 Self::Weeks(n) => format!("{} weeks", n).into(),
105 Self::Months(1) => "a month".into(),
106 Self::Months(n) => format!("{} months", n).into(),
107 Self::Years(1) => "a year".into(),
108 Self::Years(n) => format!("{} years", n).into(),
109 Self::Eternity => "eternity".into(),
110 }
111 }
112
113 fn to_text(self, accuracy: Accuracy) -> Cow<'static, str> {
114 match accuracy {
115 Accuracy::Rough => self.to_text_rough(),
116 Accuracy::Precise => self.to_text_precise(),
117 }
118 }
119}
120
121#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
123pub struct HumanTime(Duration);
124
125impl HumanTime {
126 const DAYS_IN_YEAR: i64 = 365;
127 const DAYS_IN_MONTH: i64 = 30;
128
129 pub fn now() -> Self {
132 Self(Duration::zero())
133 }
134
135 #[must_use]
137 pub fn to_text_en(self, accuracy: Accuracy, tense: Tense) -> String {
138 let mut periods = match accuracy {
139 Accuracy::Rough => self.rough_period(),
140 Accuracy::Precise => self.precise_period(),
141 };
142
143 let first = periods.remove(0).to_text(accuracy);
144 let last = periods.pop().map(|last| last.to_text(accuracy));
145
146 let mut text = periods.into_iter().fold(first, |acc, p| {
147 format!("{}, {}", acc, p.to_text(accuracy)).into()
148 });
149
150 if let Some(last) = last {
151 text = format!("{} and {}", text, last).into();
152 }
153
154 match tense {
155 Tense::Past => format!("{} ago", text),
156 Tense::Future => format!("in {}", text),
157 Tense::Present => text.into_owned(),
158 }
159 }
160
161 fn tense(self, accuracy: Accuracy) -> Tense {
162 if accuracy.is_rough() && self.0.num_seconds().abs() < 11 {
163 Tense::Present
164 } else if self.0 > Duration::zero() {
165 Tense::Future
166 } else if self.0 < Duration::zero() {
167 Tense::Past
168 } else {
169 Tense::Present
170 }
171 }
172
173 fn rough_period(self) -> Vec<TimePeriod> {
174 let period = match self.0.num_seconds().abs() {
175 n if n > 547 * S_DAY => TimePeriod::Years(max(n / S_YEAR, 2)),
176 n if n > 345 * S_DAY => TimePeriod::Years(1),
177 n if n > 45 * S_DAY => TimePeriod::Months(max(n / S_MONTH, 2)),
178 n if n > 29 * S_DAY => TimePeriod::Months(1),
179 n if n > 10 * S_DAY + 12 * S_HOUR => TimePeriod::Weeks(max(n / S_WEEK, 2)),
180 n if n > 6 * S_DAY + 12 * S_HOUR => TimePeriod::Weeks(1),
181 n if n > 36 * S_HOUR => TimePeriod::Days(max(n / S_DAY, 2)),
182 n if n > 22 * S_HOUR => TimePeriod::Days(1),
183 n if n > 90 * S_MINUTE => TimePeriod::Hours(max(n / S_HOUR, 2)),
184 n if n > 45 * S_MINUTE => TimePeriod::Hours(1),
185 n if n > 90 => TimePeriod::Minutes(max(n / S_MINUTE, 2)),
186 n if n > 45 => TimePeriod::Minutes(1),
187 n if n > 10 => TimePeriod::Seconds(n),
188 0..=10 => TimePeriod::Now,
189 _ => TimePeriod::Eternity,
190 };
191
192 vec![period]
193 }
194
195 fn precise_period(self) -> Vec<TimePeriod> {
196 let mut periods = vec![];
197
198 let (years, reminder) = self.split_years();
199 if let Some(years) = years {
200 periods.push(TimePeriod::Years(years));
201 }
202
203 let (months, reminder) = reminder.split_months();
204 if let Some(months) = months {
205 periods.push(TimePeriod::Months(months));
206 }
207
208 let (weeks, reminder) = reminder.split_weeks();
209 if let Some(weeks) = weeks {
210 periods.push(TimePeriod::Weeks(weeks));
211 }
212
213 let (days, reminder) = reminder.split_days();
214 if let Some(days) = days {
215 periods.push(TimePeriod::Days(days));
216 }
217
218 let (hours, reminder) = reminder.split_hours();
219 if let Some(hours) = hours {
220 periods.push(TimePeriod::Hours(hours));
221 }
222
223 let (minutes, reminder) = reminder.split_minutes();
224 if let Some(minutes) = minutes {
225 periods.push(TimePeriod::Minutes(minutes));
226 }
227
228 let (seconds, reminder) = reminder.split_seconds();
229 if let Some(seconds) = seconds {
230 periods.push(TimePeriod::Seconds(seconds));
231 }
232
233 let (millis, reminder) = reminder.split_milliseconds();
234 if let Some(millis) = millis {
235 periods.push(TimePeriod::Millis(millis));
236 }
237
238 let (micros, reminder) = reminder.split_microseconds();
239 if let Some(micros) = micros {
240 periods.push(TimePeriod::Micros(micros));
241 }
242
243 let (nanos, reminder) = reminder.split_nanoseconds();
244 if let Some(nanos) = nanos {
245 periods.push(TimePeriod::Nanos(nanos));
246 }
247
248 debug_assert!(reminder.is_zero());
249
250 if periods.is_empty() {
251 periods.push(TimePeriod::Seconds(0));
252 }
253
254 periods
255 }
256
257 fn split_years(self) -> (Option<i64>, Self) {
259 let years = self.0.num_days() / Self::DAYS_IN_YEAR;
260 let reminder = self.0 - Duration::days(years * Self::DAYS_IN_YEAR);
261 Self::normalize_split(years, reminder)
262 }
263
264 fn split_months(self) -> (Option<i64>, Self) {
266 let months = self.0.num_days() / Self::DAYS_IN_MONTH;
267 let reminder = self.0 - Duration::days(months * Self::DAYS_IN_MONTH);
268 Self::normalize_split(months, reminder)
269 }
270
271 fn split_weeks(self) -> (Option<i64>, Self) {
273 let weeks = self.0.num_weeks();
274 let reminder = self.0 - Duration::weeks(weeks);
275 Self::normalize_split(weeks, reminder)
276 }
277
278 fn split_days(self) -> (Option<i64>, Self) {
280 let days = self.0.num_days();
281 let reminder = self.0 - Duration::days(days);
282 Self::normalize_split(days, reminder)
283 }
284
285 fn split_hours(self) -> (Option<i64>, Self) {
287 let hours = self.0.num_hours();
288 let reminder = self.0 - Duration::hours(hours);
289 Self::normalize_split(hours, reminder)
290 }
291
292 fn split_minutes(self) -> (Option<i64>, Self) {
294 let minutes = self.0.num_minutes();
295 let reminder = self.0 - Duration::minutes(minutes);
296 Self::normalize_split(minutes, reminder)
297 }
298
299 fn split_seconds(self) -> (Option<i64>, Self) {
301 let seconds = self.0.num_seconds();
302 let reminder = self.0 - Duration::seconds(seconds);
303 Self::normalize_split(seconds, reminder)
304 }
305
306 fn split_milliseconds(self) -> (Option<i64>, Self) {
308 let millis = self.0.num_milliseconds();
309 let reminder = self.0 - Duration::milliseconds(millis);
310 Self::normalize_split(millis, reminder)
311 }
312
313 fn split_microseconds(self) -> (Option<i64>, Self) {
315 let micros = self.0.num_microseconds().unwrap_or_default();
316 let reminder = self.0 - Duration::microseconds(micros);
317 Self::normalize_split(micros, reminder)
318 }
319
320 fn split_nanoseconds(self) -> (Option<i64>, Self) {
322 let nanos = self.0.num_nanoseconds().unwrap_or_default();
323 let reminder = self.0 - Duration::nanoseconds(nanos);
324 Self::normalize_split(nanos, reminder)
325 }
326
327 fn normalize_split(wholes: impl Into<Option<i64>>, reminder: Duration) -> (Option<i64>, Self) {
328 let wholes = wholes.into().map(i64::abs).filter(|x| *x > 0);
329 (wholes, Self(reminder))
330 }
331
332 pub fn is_zero(self) -> bool {
333 self.0.is_zero()
334 }
335
336 fn locale_en(&self, accuracy: Accuracy) -> String {
337 let tense = self.tense(accuracy);
338 self.to_text_en(accuracy, tense)
339 }
340}
341
342impl fmt::Display for HumanTime {
343 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344 let accuracy = if f.alternate() {
345 Accuracy::Precise
346 } else {
347 Accuracy::Rough
348 };
349
350 f.pad(&self.locale_en(accuracy))
351 }
352}
353
354impl From<Duration> for HumanTime {
355 fn from(duration: Duration) -> Self {
356 Self(duration)
357 }
358}
359
360impl<TZ> From<DateTime<TZ>> for HumanTime
361where
362 TZ: TimeZone,
363{
364 fn from(dt: DateTime<TZ>) -> Self {
365 dt.signed_duration_since(Utc::now()).into()
366 }
367}
368
369impl From<SystemTime> for HumanTime {
370 fn from(st: SystemTime) -> Self {
371 DateTime::<Utc>::from(st).into()
372 }
373}
374
375impl Humanize for Duration {
376 fn humanize(&self) -> String {
377 format!("{}", HumanTime::from(*self))
378 }
379}
380
381impl<TZ> Humanize for DateTime<TZ>
382where
383 TZ: TimeZone,
384{
385 fn humanize(&self) -> String {
386 format!("{}", HumanTime::from(self.clone()))
387 }
388}
389
390impl Humanize for SystemTime {
391 fn humanize(&self) -> String {
392 HumanTime::from(*self).to_string()
393 }
394}