1const MINUTE: i64 = 60;
13const HOUR: i64 = 60 * MINUTE;
14const DAY: i64 = 24 * HOUR;
15const WEEK: i64 = 7 * DAY;
16const MONTH: i64 = 30 * DAY; const YEAR: i64 = 365 * DAY;
18
19pub fn format_relative(diff_secs: i64) -> String {
23 let past = diff_secs >= 0;
24 let abs = diff_secs.abs();
25
26 if abs < 45 {
28 return if past {
29 "just now".to_string()
30 } else {
31 "any moment now".to_string()
32 };
33 }
34
35 if abs < HOUR {
37 let n = (abs + MINUTE / 2) / MINUTE;
38 let n = n.max(1);
39 return phrase(
40 past,
41 &format!("{n} minute{s} ago", s = s(n)),
42 &format!("in {n} minute{s}", s = s(n)),
43 );
44 }
45
46 if abs < DAY {
48 let n = (abs + HOUR / 2) / HOUR;
49 let n = n.max(1);
50 return phrase(
51 past,
52 &match n {
53 1 => "an hour ago".to_string(),
54 _ => format!("{n} hours ago"),
55 },
56 &match n {
57 1 => "in an hour".to_string(),
58 _ => format!("in {n} hours"),
59 },
60 );
61 }
62
63 if abs < 2 * DAY {
65 return phrase(past, "yesterday", "tomorrow");
66 }
67
68 if abs < WEEK {
70 let n = abs / DAY;
71 return phrase(past, &format!("{n} days ago"), &format!("in {n} days"));
72 }
73
74 if abs < 2 * WEEK {
76 return phrase(past, "last week", "next week");
77 }
78
79 if abs < MONTH {
81 let n = abs / WEEK;
82 return phrase(past, &format!("{n} weeks ago"), &format!("in {n} weeks"));
83 }
84
85 if abs < 2 * MONTH {
87 return phrase(past, "last month", "next month");
88 }
89
90 if abs < YEAR {
92 let n = abs / MONTH;
93 return phrase(past, &format!("{n} months ago"), &format!("in {n} months"));
94 }
95
96 if abs < 2 * YEAR {
98 return phrase(past, "last year", "next year");
99 }
100
101 let n = abs / YEAR;
103 phrase(past, &format!("{n} years ago"), &format!("in {n} years"))
104}
105
106pub fn format_since_last(diff_secs: i64) -> String {
115 if diff_secs <= 0 {
116 return "at the same time".to_string();
117 }
118
119 if diff_secs < 60 {
120 return "moments later".to_string();
121 }
122
123 if diff_secs < HOUR {
124 let n = (diff_secs + MINUTE / 2) / MINUTE;
125 let n = n.max(1);
126 return match n {
127 1 => "a minute later".to_string(),
128 _ => format!("{n} minutes later"),
129 };
130 }
131
132 if diff_secs < DAY {
133 let n = (diff_secs + HOUR / 2) / HOUR;
134 let n = n.max(1);
135 if n < 6 {
137 return match n {
138 1 => "an hour later".to_string(),
139 _ => format!("{n} hours later"),
140 };
141 }
142 return "later that day".to_string();
143 }
144
145 if diff_secs < 2 * DAY {
146 return "the next day".to_string();
147 }
148
149 if diff_secs < WEEK {
150 let n = diff_secs / DAY;
151 return format!("{n} days later");
152 }
153
154 if diff_secs < 2 * WEEK {
155 return "the following week".to_string();
156 }
157
158 if diff_secs < MONTH {
159 let n = diff_secs / WEEK;
160 return format!("{n} weeks later");
161 }
162
163 if diff_secs < 2 * MONTH {
164 return "the following month".to_string();
165 }
166
167 if diff_secs < YEAR {
168 let n = diff_secs / MONTH;
169 return format!("{n} months later");
170 }
171
172 if diff_secs < 2 * YEAR {
173 return "the following year".to_string();
174 }
175
176 let n = diff_secs / YEAR;
177 format!("{n} years later")
178}
179
180fn s(n: i64) -> &'static str {
181 if n == 1 { "" } else { "s" }
182}
183
184fn phrase(past: bool, past_form: &str, future_form: &str) -> String {
185 if past {
186 past_form.to_string()
187 } else {
188 future_form.to_string()
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn just_now_for_small_past_and_future() {
198 assert_eq!(format_relative(0), "just now");
199 assert_eq!(format_relative(30), "just now");
200 assert_eq!(format_relative(-30), "any moment now");
201 }
202
203 #[test]
204 fn minutes() {
205 assert_eq!(format_relative(60), "1 minute ago");
206 assert_eq!(format_relative(300), "5 minutes ago");
207 assert_eq!(format_relative(-600), "in 10 minutes");
208 }
209
210 #[test]
211 fn hours() {
212 assert_eq!(format_relative(3600), "an hour ago");
213 assert_eq!(format_relative(3 * 3600), "3 hours ago");
214 assert_eq!(format_relative(-3600), "in an hour");
215 }
216
217 #[test]
218 fn yesterday_and_tomorrow() {
219 assert_eq!(format_relative(DAY + 3600), "yesterday");
220 assert_eq!(format_relative(-(DAY + 3600)), "tomorrow");
221 }
222
223 #[test]
224 fn days() {
225 assert_eq!(format_relative(3 * DAY), "3 days ago");
226 assert_eq!(format_relative(-5 * DAY), "in 5 days");
227 }
228
229 #[test]
230 fn last_week() {
231 assert_eq!(format_relative(WEEK + DAY), "last week");
232 assert_eq!(format_relative(-(WEEK + DAY)), "next week");
233 }
234
235 #[test]
236 fn weeks() {
237 assert_eq!(format_relative(3 * WEEK), "3 weeks ago");
238 }
239
240 #[test]
241 fn months_and_years() {
242 assert_eq!(format_relative(2 * MONTH), "2 months ago");
243 assert_eq!(format_relative(3 * YEAR), "3 years ago");
244 assert_eq!(format_relative(-(2 * YEAR + DAY)), "in 2 years");
245 }
246
247 #[test]
248 fn last_month_and_next_month() {
249 assert_eq!(format_relative(MONTH + DAY), "last month");
250 assert_eq!(format_relative(-(MONTH + DAY)), "next month");
251 }
252
253 #[test]
256 fn since_last_zero_or_negative_is_at_the_same_time() {
257 assert_eq!(format_since_last(0), "at the same time");
258 assert_eq!(format_since_last(-1), "at the same time");
259 assert_eq!(format_since_last(-3600), "at the same time");
260 }
261
262 #[test]
263 fn since_last_sub_minute_is_moments_later() {
264 assert_eq!(format_since_last(1), "moments later");
265 assert_eq!(format_since_last(59), "moments later");
266 }
267
268 #[test]
269 fn since_last_one_minute() {
270 assert_eq!(format_since_last(60), "a minute later");
271 assert_eq!(format_since_last(89), "a minute later");
273 assert_eq!(format_since_last(90), "2 minutes later");
274 }
275
276 #[test]
277 fn since_last_minutes() {
278 assert_eq!(format_since_last(2 * MINUTE), "2 minutes later");
279 assert_eq!(format_since_last(3599), "60 minutes later");
280 }
281
282 #[test]
283 fn since_last_one_hour() {
284 assert_eq!(format_since_last(HOUR), "an hour later");
285 assert_eq!(format_since_last(HOUR + MINUTE * 25), "an hour later"); }
287
288 #[test]
289 fn since_last_hours() {
290 assert_eq!(format_since_last(2 * HOUR), "2 hours later");
291 assert_eq!(format_since_last(5 * HOUR), "5 hours later");
292 }
293
294 #[test]
295 fn since_last_later_that_day() {
296 assert_eq!(format_since_last(6 * HOUR), "later that day");
297 assert_eq!(format_since_last(12 * HOUR), "later that day");
298 assert_eq!(format_since_last(23 * HOUR), "later that day");
299 }
300
301 #[test]
302 fn since_last_the_next_day() {
303 assert_eq!(format_since_last(DAY + 1), "the next day");
304 assert_eq!(format_since_last(2 * DAY - 1), "the next day");
305 }
306
307 #[test]
308 fn since_last_days() {
309 assert_eq!(format_since_last(3 * DAY), "3 days later");
310 assert_eq!(format_since_last(6 * DAY), "6 days later");
311 }
312
313 #[test]
314 fn since_last_the_following_week() {
315 assert_eq!(format_since_last(WEEK + 1), "the following week");
316 assert_eq!(format_since_last(13 * DAY), "the following week");
317 }
318
319 #[test]
320 fn since_last_weeks() {
321 assert_eq!(format_since_last(3 * WEEK), "3 weeks later");
322 }
323
324 #[test]
325 fn since_last_the_following_month() {
326 assert_eq!(format_since_last(MONTH + 1), "the following month");
327 }
328
329 #[test]
330 fn since_last_months() {
331 assert_eq!(format_since_last(3 * MONTH), "3 months later");
332 }
333
334 #[test]
335 fn since_last_the_following_year() {
336 assert_eq!(format_since_last(YEAR + 1), "the following year");
337 }
338
339 #[test]
340 fn since_last_years() {
341 assert_eq!(format_since_last(3 * YEAR), "3 years later");
342 }
343}