1use chrono::NaiveDateTime;
2
3fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 {
4 let duration = date.signed_duration_since(base_date);
5 duration.num_seconds()
6}
7
8fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String {
9 let suffix = if distance < 0 { " from now" } else { " ago" };
10
11 let d = distance.abs();
12
13 let minutes = d / 60;
14 let hours = d / 3600;
15 let days = d / 86400;
16 let months = d / 2592000;
17 let years = d / 31536000;
18
19 let string = if d < 5 && include_seconds {
20 "less than 5 seconds".to_string()
21 } else if d < 10 && include_seconds {
22 "less than 10 seconds".to_string()
23 } else if d < 20 && include_seconds {
24 "less than 20 seconds".to_string()
25 } else if d < 40 && include_seconds {
26 "half a minute".to_string()
27 } else if d < 60 && include_seconds {
28 "less than a minute".to_string()
29 } else if d < 90 && include_seconds {
30 "1 minute".to_string()
31 } else if d < 30 {
32 "less than a minute".to_string()
33 } else if d < 90 {
34 "1 minute".to_string()
35 } else if d < 2700 {
36 format!("{} minutes", minutes)
37 } else if d < 5400 {
38 "about 1 hour".to_string()
39 } else if d < 86400 {
40 format!("about {} hours", hours)
41 } else if d < 172800 {
42 "1 day".to_string()
43 } else if d < 2592000 {
44 format!("{} days", days)
45 } else if d < 5184000 {
46 "about 1 month".to_string()
47 } else if d < 7776000 {
48 "about 2 months".to_string()
49 } else if d < 31536000 {
50 format!("{} months", months)
51 } else if d < 39408000 {
52 "about 1 year".to_string()
53 } else if d < 47318400 {
54 "over 1 year".to_string()
55 } else if d < 63072000 {
56 "almost 2 years".to_string()
57 } else {
58 format!("{} years", years)
59 };
60
61 if add_suffix {
62 return format!("{}{}", string, suffix);
63 } else {
64 string
65 }
66}
67
68pub fn naive_format_distance(
69 date: NaiveDateTime,
70 base_date: NaiveDateTime,
71 include_seconds: bool,
72 add_suffix: bool,
73) -> String {
74 let distance = distance_in_seconds(date, base_date);
75
76 distance_string(distance, include_seconds, add_suffix)
77}
78
79pub fn naive_format_distance_from_now(
80 datetime: NaiveDateTime,
81 include_seconds: bool,
82 add_suffix: bool,
83) -> String {
84 let now = chrono::offset::Local::now().naive_local();
85
86 naive_format_distance(datetime, now, include_seconds, add_suffix)
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use chrono::NaiveDateTime;
93
94 // #[test]
95 // fn test_naive_format_distance() {
96 // let date = NaiveDateTime::from_timestamp_opt(0, 0);
97 // let base_date = NaiveDateTime::from_timestamp_opt(9400, 0);
98
99 // assert_eq!(
100 // "about 2 hours ago",
101 // naive_format_distance(
102 // date.expect("invalid datetime"),
103 // base_date.expect("invalid datetime"),
104 // false,
105 // true
106 // )
107 // );
108 // }
109
110 // #[test]
111 // fn test_naive_format_distance_with_seconds() {
112 // let date = NaiveDateTime::from_timestamp_opt(0, 0);
113 // let base_date = NaiveDateTime::from_timestamp_opt(10, 0);
114
115 // assert_eq!(
116 // "less than 30 seconds ago",
117 // naive_format_distance(
118 // date.expect("invalid datetime"),
119 // base_date.expect("invalid datetime"),
120 // true,
121 // true
122 // )
123 // );
124 // }
125
126 // #[test]
127 // fn test_naive_format_distance_with_future_date() {
128 // let date = NaiveDateTime::from_timestamp_opt(3400, 0);
129 // let base_date = NaiveDateTime::from_timestamp_opt(00, 0);
130
131 // assert_eq!(
132 // "about 56 from now",
133 // naive_format_distance(
134 // date.expect("invalid datetime"),
135 // base_date.expect("invalid datetime"),
136 // false,
137 // true
138 // )
139 // );
140 // }
141
142 #[test]
143 fn test_naive_format_distance_string() {
144 assert_eq!(distance_string(3, false, false), "less than a minute");
145 assert_eq!(distance_string(7, false, false), "less than a minute");
146 assert_eq!(distance_string(13, false, false), "less than a minute");
147 assert_eq!(distance_string(21, false, false), "less than a minute");
148 assert_eq!(distance_string(45, false, false), "1 minute");
149 assert_eq!(distance_string(61, false, false), "1 minute");
150 assert_eq!(distance_string(1920, false, false), "32 minutes");
151 assert_eq!(distance_string(3902, false, false), "about 1 hour");
152 assert_eq!(distance_string(18002, false, false), "about 5 hours");
153 assert_eq!(distance_string(86470, false, false), "1 day");
154 assert_eq!(distance_string(345880, false, false), "4 days");
155 }
156
157 #[test]
158 fn test_naive_format_distance_string_include_seconds() {
159 assert_eq!(distance_string(3, true, false), "less than 5 seconds");
160 assert_eq!(distance_string(7, true, false), "less than 10 seconds");
161 assert_eq!(distance_string(13, true, false), "less than 20 seconds");
162 assert_eq!(distance_string(21, true, false), "half a minute");
163 assert_eq!(distance_string(45, true, false), "less than a minute");
164 assert_eq!(distance_string(61, true, false), "1 minute");
165 }
166}