1use chrono::prelude::*;
2use std::time::Duration;
3
4#[derive(Debug)]
5struct RelativeTimeMagnitude {
6 duration: Duration,
7 fmt: &'static str,
8 div_by: Duration,
9}
10
11const MINUTE_SECS: u64 = 60;
12const HOUR_SECS: u64 = MINUTE_SECS * 60;
13const DAY_SECS: u64 = HOUR_SECS * 24;
14const WEEK_SECS: u64 = DAY_SECS * 7;
15const MONTH_SECS: u64 = DAY_SECS * 30;
16const YEAR_SECS: u64 = MONTH_SECS * 12;
17const LONG_TIME_SECS: u64 = YEAR_SECS * 37;
18
19const DEFAULT_MAGNITUDES: [RelativeTimeMagnitude; 17] = [
20 RelativeTimeMagnitude {
22 duration: Duration::SECOND,
23 fmt: "now",
24 div_by: Duration::SECOND,
25 },
26 RelativeTimeMagnitude {
28 duration: Duration::from_secs(2),
29 fmt: "1 second {label}",
30 div_by: Duration::from_nanos(1),
31 },
32 RelativeTimeMagnitude {
34 duration: Duration::from_secs(MINUTE_SECS),
35 fmt: "{amt} seconds {label}",
36 div_by: Duration::SECOND,
37 },
38 RelativeTimeMagnitude {
40 duration: Duration::from_secs(MINUTE_SECS * 2),
41 fmt: "1 minute {label}",
42 div_by: Duration::from_nanos(1),
43 },
44 RelativeTimeMagnitude {
46 duration: Duration::from_secs(HOUR_SECS),
47 fmt: "{amt} minutes {label}",
48 div_by: Duration::from_secs(MINUTE_SECS),
49 },
50 RelativeTimeMagnitude {
52 duration: Duration::from_secs(HOUR_SECS * 2),
53 fmt: "1 hour {label}",
54 div_by: Duration::from_nanos(1),
55 },
56 RelativeTimeMagnitude {
58 duration: Duration::from_secs(DAY_SECS),
59 fmt: "{amt} hours {label}",
60 div_by: Duration::from_secs(HOUR_SECS),
61 },
62 RelativeTimeMagnitude {
64 duration: Duration::from_secs(DAY_SECS * 2),
65 fmt: "1 day {label}",
66 div_by: Duration::from_nanos(1),
67 },
68 RelativeTimeMagnitude {
70 duration: Duration::from_secs(WEEK_SECS),
71 fmt: "{amt} days {label}",
72 div_by: Duration::from_secs(DAY_SECS),
73 },
74 RelativeTimeMagnitude {
76 duration: Duration::from_secs(WEEK_SECS * 2),
77 fmt: "1 week {label}",
78 div_by: Duration::from_nanos(1),
79 },
80 RelativeTimeMagnitude {
82 duration: Duration::from_secs(MONTH_SECS),
83 fmt: "{amt} weeks {label}",
84 div_by: Duration::from_secs(WEEK_SECS),
85 },
86 RelativeTimeMagnitude {
88 duration: Duration::from_secs(MONTH_SECS * 2),
89 fmt: "1 month {label}",
90 div_by: Duration::from_nanos(1),
91 },
92 RelativeTimeMagnitude {
94 duration: Duration::from_secs(YEAR_SECS),
95 fmt: "{amt} months {label}",
96 div_by: Duration::from_secs(MONTH_SECS),
97 },
98 RelativeTimeMagnitude {
100 duration: Duration::from_secs(MONTH_SECS * 18),
101 fmt: "1 year {label}",
102 div_by: Duration::from_nanos(1),
103 },
104 RelativeTimeMagnitude {
106 duration: Duration::from_secs(YEAR_SECS * 2),
107 fmt: "2 years {label}",
108 div_by: Duration::from_nanos(1),
109 },
110 RelativeTimeMagnitude {
112 duration: Duration::from_secs(LONG_TIME_SECS),
113 fmt: "{amt} years {label}",
114 div_by: Duration::from_secs(YEAR_SECS),
115 },
116 RelativeTimeMagnitude {
118 duration: Duration::from_secs(std::u64::MAX),
119 fmt: "a long while {label}",
120 div_by: Duration::from_nanos(1),
121 },
122];
123
124pub fn format(then: DateTime<Utc>) -> String {
125 let now = Utc::now();
126 return format_rel(then, now, "ago", "from now");
127}
128
129pub fn format_rel(a: DateTime<Utc>, b: DateTime<Utc>, a_label: &str, b_label: &str) -> String {
130 let (diff, label) = if a > b {
131 ((a - b).to_std().unwrap(), b_label)
132 } else {
133 ((b - a).to_std().unwrap(), a_label)
134 };
135
136 let mut magnitude = &DEFAULT_MAGNITUDES[0];
137 for mag in &DEFAULT_MAGNITUDES {
138 if mag.duration > diff {
139 magnitude = mag;
140 break;
141 }
142 }
143
144 let amt_str = (diff.as_nanos() / magnitude.div_by.as_nanos()).to_string();
145 let replace_amt = magnitude.fmt.replace("{amt}", &amt_str);
146 replace_amt.replace("{label}", label)
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn test_format_rel() {
155 use chrono::Duration;
156
157 struct TimeTest(DateTime<Utc>, &'static str);
158
159 let now = Utc::now();
160 let tests = vec![
161 TimeTest(now - Duration::seconds(1), "1 second ago"),
163 TimeTest(now + Duration::seconds(1), "1 second from now"),
164 TimeTest(now - Duration::seconds(5), "5 seconds ago"),
166 TimeTest(now + Duration::seconds(5), "5 seconds from now"),
167 TimeTest(now - Duration::minutes(1), "1 minute ago"),
169 TimeTest(now + Duration::minutes(1), "1 minute from now"),
170 TimeTest(now - Duration::minutes(5), "5 minutes ago"),
172 TimeTest(now + Duration::minutes(5), "5 minutes from now"),
173 TimeTest(now - Duration::hours(1), "1 hour ago"),
175 TimeTest(now + Duration::hours(1), "1 hour from now"),
176 TimeTest(now - Duration::hours(5), "5 hours ago"),
178 TimeTest(now + Duration::hours(5), "5 hours from now"),
179 TimeTest(now - Duration::days(1), "1 day ago"),
181 TimeTest(now + Duration::days(1), "1 day from now"),
182 TimeTest(now - Duration::days(5), "5 days ago"),
184 TimeTest(now + Duration::days(5), "5 days from now"),
185 TimeTest(now - Duration::weeks(1), "1 week ago"),
187 TimeTest(now + Duration::weeks(1), "1 week from now"),
188 TimeTest(now - Duration::weeks(3), "3 weeks ago"),
190 TimeTest(now + Duration::weeks(3), "3 weeks from now"),
191 TimeTest(now - Duration::days(30), "1 month ago"),
193 TimeTest(now + Duration::days(30), "1 month from now"),
194 TimeTest(now - Duration::days(30 * 5), "5 months ago"),
196 TimeTest(now + Duration::days(30 * 5), "5 months from now"),
197 TimeTest(now - Duration::days(365), "1 year ago"),
199 TimeTest(now + Duration::days(365), "1 year from now"),
200 TimeTest(now - Duration::days(365 * 5), "5 years ago"),
202 TimeTest(now + Duration::days(365 * 5), "5 years from now"),
203 TimeTest(now - Duration::days(365 * 1000), "a long while ago"),
205 TimeTest(now + Duration::days(365 * 1000), "a long while from now"),
206 ];
207
208 for test in tests.iter() {
209 let fmted = format_rel(test.0, now, "ago", "from now");
210 assert_eq!(fmted, test.1);
211 }
212 }
213
214 extern crate test;
215 use test::Bencher;
216
217 #[bench]
218 fn bench_format_rel(b: &mut Bencher) {
219 use chrono::Duration;
220
221 let now = Utc::now();
222 let then = now - Duration::days(30);
223
224 b.iter(|| format_rel(then, now, "ago", "from now"))
225 }
226}