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 < 31540000 {
50 format!("{} months", months)
51 } else if d < 39425000 {
52 "about 1 year".to_string()
53 } else if d < 55195000 {
54 "over 1 year".to_string()
55 } else if d < 63080000 {
56 "almost 2 years".to_string()
57 } else {
58 let years = d / 31536000;
59 let remaining_months = (d % 31536000) / 2592000;
60
61 if remaining_months < 3 {
62 format!("about {} years", years)
63 } else if remaining_months < 9 {
64 format!("over {} years", years)
65 } else {
66 format!("almost {} years", years + 1)
67 }
68 };
69
70 if add_suffix {
71 return format!("{}{}", string, suffix);
72 } else {
73 string
74 }
75}
76
77pub fn naive_format_distance(
78 date: NaiveDateTime,
79 base_date: NaiveDateTime,
80 include_seconds: bool,
81 add_suffix: bool,
82) -> String {
83 let distance = distance_in_seconds(date, base_date);
84
85 distance_string(distance, include_seconds, add_suffix)
86}
87
88pub fn naive_format_distance_from_now(
89 datetime: NaiveDateTime,
90 include_seconds: bool,
91 add_suffix: bool,
92) -> String {
93 let now = chrono::offset::Local::now().naive_local();
94
95 naive_format_distance(datetime, now, include_seconds, add_suffix)
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use chrono::NaiveDateTime;
102
103 #[test]
104 fn test_naive_format_distance() {
105 let date =
106 NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date");
107 let base_date =
108 NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date");
109
110 assert_eq!(
111 "about 2 hours",
112 naive_format_distance(date, base_date, false, false)
113 );
114 }
115
116 #[test]
117 fn test_naive_format_distance_with_suffix() {
118 let date =
119 NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date");
120 let base_date =
121 NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date");
122
123 assert_eq!(
124 "about 2 hours from now",
125 naive_format_distance(date, base_date, false, true)
126 );
127 }
128
129 #[test]
130 fn test_naive_format_distance_from_now() {
131 let date = NaiveDateTime::parse_from_str("1969-07-20T00:00:00Z", "%Y-%m-%dT%H:%M:%SZ")
132 .expect("Invalid NaiveDateTime for date");
133
134 assert_eq!(
135 "over 54 years ago",
136 naive_format_distance_from_now(date, false, true)
137 );
138 }
139
140 #[test]
141 fn test_naive_format_distance_string() {
142 assert_eq!(distance_string(3, false, false), "less than a minute");
143 assert_eq!(distance_string(7, false, false), "less than a minute");
144 assert_eq!(distance_string(13, false, false), "less than a minute");
145 assert_eq!(distance_string(21, false, false), "less than a minute");
146 assert_eq!(distance_string(45, false, false), "1 minute");
147 assert_eq!(distance_string(61, false, false), "1 minute");
148 assert_eq!(distance_string(1920, false, false), "32 minutes");
149 assert_eq!(distance_string(3902, false, false), "about 1 hour");
150 assert_eq!(distance_string(18002, false, false), "about 5 hours");
151 assert_eq!(distance_string(86470, false, false), "1 day");
152 assert_eq!(distance_string(345880, false, false), "4 days");
153 assert_eq!(distance_string(2764800, false, false), "about 1 month");
154 assert_eq!(distance_string(5184000, false, false), "about 2 months");
155 assert_eq!(distance_string(10368000, false, false), "4 months");
156 assert_eq!(distance_string(34694000, false, false), "about 1 year");
157 assert_eq!(distance_string(47310000, false, false), "over 1 year");
158 assert_eq!(distance_string(61503000, false, false), "almost 2 years");
159 assert_eq!(distance_string(160854000, false, false), "about 5 years");
160 assert_eq!(distance_string(236550000, false, false), "over 7 years");
161 assert_eq!(distance_string(249166000, false, false), "almost 8 years");
162 }
163
164 #[test]
165 fn test_naive_format_distance_string_include_seconds() {
166 assert_eq!(distance_string(3, true, false), "less than 5 seconds");
167 assert_eq!(distance_string(7, true, false), "less than 10 seconds");
168 assert_eq!(distance_string(13, true, false), "less than 20 seconds");
169 assert_eq!(distance_string(21, true, false), "half a minute");
170 assert_eq!(distance_string(45, true, false), "less than a minute");
171 assert_eq!(distance_string(61, true, false), "1 minute");
172 }
173}