rs_humanize/
time.rs

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    // now
21    RelativeTimeMagnitude {
22        duration: Duration::SECOND,
23        fmt: "now",
24        div_by: Duration::SECOND,
25    },
26    // 1 second ago
27    RelativeTimeMagnitude {
28        duration: Duration::from_secs(2),
29        fmt: "1 second {label}",
30        div_by: Duration::from_nanos(1),
31    },
32    // 10 seconds ago
33    RelativeTimeMagnitude {
34        duration: Duration::from_secs(MINUTE_SECS),
35        fmt: "{amt} seconds {label}",
36        div_by: Duration::SECOND,
37    },
38    // 1 minute ago
39    RelativeTimeMagnitude {
40        duration: Duration::from_secs(MINUTE_SECS * 2),
41        fmt: "1 minute {label}",
42        div_by: Duration::from_nanos(1),
43    },
44    // 10 minutes ago
45    RelativeTimeMagnitude {
46        duration: Duration::from_secs(HOUR_SECS),
47        fmt: "{amt} minutes {label}",
48        div_by: Duration::from_secs(MINUTE_SECS),
49    },
50    // 1 hour ago
51    RelativeTimeMagnitude {
52        duration: Duration::from_secs(HOUR_SECS * 2),
53        fmt: "1 hour {label}",
54        div_by: Duration::from_nanos(1),
55    },
56    // 10 hours ago
57    RelativeTimeMagnitude {
58        duration: Duration::from_secs(DAY_SECS),
59        fmt: "{amt} hours {label}",
60        div_by: Duration::from_secs(HOUR_SECS),
61    },
62    // 1 day ago
63    RelativeTimeMagnitude {
64        duration: Duration::from_secs(DAY_SECS * 2),
65        fmt: "1 day {label}",
66        div_by: Duration::from_nanos(1),
67    },
68    // 10 days ago
69    RelativeTimeMagnitude {
70        duration: Duration::from_secs(WEEK_SECS),
71        fmt: "{amt} days {label}",
72        div_by: Duration::from_secs(DAY_SECS),
73    },
74    // 1 week ago
75    RelativeTimeMagnitude {
76        duration: Duration::from_secs(WEEK_SECS * 2),
77        fmt: "1 week {label}",
78        div_by: Duration::from_nanos(1),
79    },
80    // 10 weeks ago
81    RelativeTimeMagnitude {
82        duration: Duration::from_secs(MONTH_SECS),
83        fmt: "{amt} weeks {label}",
84        div_by: Duration::from_secs(WEEK_SECS),
85    },
86    // 1 month ago
87    RelativeTimeMagnitude {
88        duration: Duration::from_secs(MONTH_SECS * 2),
89        fmt: "1 month {label}",
90        div_by: Duration::from_nanos(1),
91    },
92    // 10 months ago
93    RelativeTimeMagnitude {
94        duration: Duration::from_secs(YEAR_SECS),
95        fmt: "{amt} months {label}",
96        div_by: Duration::from_secs(MONTH_SECS),
97    },
98    // 1 year ago
99    RelativeTimeMagnitude {
100        duration: Duration::from_secs(MONTH_SECS * 18),
101        fmt: "1 year {label}",
102        div_by: Duration::from_nanos(1),
103    },
104    // 2 years ago
105    RelativeTimeMagnitude {
106        duration: Duration::from_secs(YEAR_SECS * 2),
107        fmt: "2 years {label}",
108        div_by: Duration::from_nanos(1),
109    },
110    // 10 years ago
111    RelativeTimeMagnitude {
112        duration: Duration::from_secs(LONG_TIME_SECS),
113        fmt: "{amt} years {label}",
114        div_by: Duration::from_secs(YEAR_SECS),
115    },
116    // a long while ago
117    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            // singular second
162            TimeTest(now - Duration::seconds(1), "1 second ago"),
163            TimeTest(now + Duration::seconds(1), "1 second from now"),
164            // multiple seconds
165            TimeTest(now - Duration::seconds(5), "5 seconds ago"),
166            TimeTest(now + Duration::seconds(5), "5 seconds from now"),
167            // singular minute
168            TimeTest(now - Duration::minutes(1), "1 minute ago"),
169            TimeTest(now + Duration::minutes(1), "1 minute from now"),
170            // multiple minutes
171            TimeTest(now - Duration::minutes(5), "5 minutes ago"),
172            TimeTest(now + Duration::minutes(5), "5 minutes from now"),
173            // singular hour
174            TimeTest(now - Duration::hours(1), "1 hour ago"),
175            TimeTest(now + Duration::hours(1), "1 hour from now"),
176            // multiple hours
177            TimeTest(now - Duration::hours(5), "5 hours ago"),
178            TimeTest(now + Duration::hours(5), "5 hours from now"),
179            // singular day
180            TimeTest(now - Duration::days(1), "1 day ago"),
181            TimeTest(now + Duration::days(1), "1 day from now"),
182            // multiple days
183            TimeTest(now - Duration::days(5), "5 days ago"),
184            TimeTest(now + Duration::days(5), "5 days from now"),
185            // singular week
186            TimeTest(now - Duration::weeks(1), "1 week ago"),
187            TimeTest(now + Duration::weeks(1), "1 week from now"),
188            // multiple weeks
189            TimeTest(now - Duration::weeks(3), "3 weeks ago"),
190            TimeTest(now + Duration::weeks(3), "3 weeks from now"),
191            // singular month
192            TimeTest(now - Duration::days(30), "1 month ago"),
193            TimeTest(now + Duration::days(30), "1 month from now"),
194            // multiple months
195            TimeTest(now - Duration::days(30 * 5), "5 months ago"),
196            TimeTest(now + Duration::days(30 * 5), "5 months from now"),
197            // singular year
198            TimeTest(now - Duration::days(365), "1 year ago"),
199            TimeTest(now + Duration::days(365), "1 year from now"),
200            // multiple years
201            TimeTest(now - Duration::days(365 * 5), "5 years ago"),
202            TimeTest(now + Duration::days(365 * 5), "5 years from now"),
203            // long time
204            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}