1use std::fmt::Write;
7
8use chrono::{DateTime, Duration, Local, TimeZone, Utc};
9
10use crate::error::message::MessageError;
11
12const SEPARATOR: &str = ", ";
13
14pub const TIMESTAMP_FACTOR: i64 = 1_000_000_000;
19
20#[must_use]
33pub fn get_offset() -> i64 {
34 Utc.with_ymd_and_hms(2001, 1, 1, 0, 0, 0)
35 .unwrap()
36 .timestamp()
37}
38
39pub fn get_local_time(date_stamp: &i64, offset: &i64) -> Result<DateTime<Local>, MessageError> {
53 let utc_stamp = DateTime::from_timestamp((date_stamp / TIMESTAMP_FACTOR) + offset, 0)
54 .ok_or(MessageError::InvalidTimestamp(*date_stamp))?
55 .naive_utc();
56 Ok(Local.from_utc_datetime(&utc_stamp))
57}
58
59#[must_use]
71pub fn format(date: &Result<DateTime<Local>, MessageError>) -> String {
72 match date {
73 Ok(d) => DateTime::format(d, "%b %d, %Y %l:%M:%S %p").to_string(),
74 Err(why) => why.to_string(),
75 }
76}
77
78#[must_use]
91pub fn readable_diff(
92 start: Result<DateTime<Local>, MessageError>,
93 end: Result<DateTime<Local>, MessageError>,
94) -> Option<String> {
95 let diff: Duration = end.ok()? - start.ok()?;
97 let seconds = diff.num_seconds();
98
99 if seconds < 0 {
101 return None;
102 }
103
104 let mut out_s = String::with_capacity(42);
108
109 let days = seconds / 86400;
110 let hours = (seconds % 86400) / 3600;
111 let minutes = (seconds % 86400 % 3600) / 60;
112 let secs = seconds % 86400 % 3600 % 60;
113
114 if days != 0 {
115 let metric = match days {
116 1 => "day",
117 _ => "days",
118 };
119 let _ = write!(out_s, "{days} {metric}");
120 }
121 if hours != 0 {
122 let metric = match hours {
123 1 => "hour",
124 _ => "hours",
125 };
126 if !out_s.is_empty() {
127 out_s.push_str(SEPARATOR);
128 }
129 let _ = write!(out_s, "{hours} {metric}");
130 }
131 if minutes != 0 {
132 let metric = match minutes {
133 1 => "minute",
134 _ => "minutes",
135 };
136 if !out_s.is_empty() {
137 out_s.push_str(SEPARATOR);
138 }
139 let _ = write!(out_s, "{minutes} {metric}");
140 }
141 if secs != 0 {
142 let metric = match secs {
143 1 => "second",
144 _ => "seconds",
145 };
146 if !out_s.is_empty() {
147 out_s.push_str(SEPARATOR);
148 }
149 let _ = write!(out_s, "{secs} {metric}");
150 }
151 Some(out_s)
152}
153
154#[cfg(test)]
155mod tests {
156 use crate::{
157 error::message::MessageError,
158 util::dates::{format, readable_diff},
159 };
160 use chrono::prelude::*;
161
162 #[test]
163 fn can_format_date_single_digit() {
164 let date = Local
165 .with_ymd_and_hms(2020, 5, 20, 9, 10, 11)
166 .single()
167 .ok_or(MessageError::InvalidTimestamp(0));
168 assert_eq!(format(&date), "May 20, 2020 9:10:11 AM");
169 }
170
171 #[test]
172 fn can_format_date_double_digit() {
173 let date = Local
174 .with_ymd_and_hms(2020, 5, 20, 10, 10, 11)
175 .single()
176 .ok_or(MessageError::InvalidTimestamp(0));
177 assert_eq!(format(&date), "May 20, 2020 10:10:11 AM");
178 }
179
180 #[test]
181 fn cant_format_diff_backwards() {
182 let end = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 11).unwrap());
183 let start = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 30).unwrap());
184 assert_eq!(readable_diff(start, end), None);
185 }
186
187 #[test]
188 fn can_format_diff_all_singular() {
189 let start = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 11).unwrap());
190 let end = Ok(Local.with_ymd_and_hms(2020, 5, 21, 10, 11, 12).unwrap());
191 assert_eq!(
192 readable_diff(start, end),
193 Some("1 day, 1 hour, 1 minute, 1 second".to_owned())
194 );
195 }
196
197 #[test]
198 fn can_format_diff_mixed_singular() {
199 let start = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 11).unwrap());
200 let end = Ok(Local.with_ymd_and_hms(2020, 5, 22, 10, 20, 12).unwrap());
201 assert_eq!(
202 readable_diff(start, end),
203 Some("2 days, 1 hour, 10 minutes, 1 second".to_owned())
204 );
205 }
206
207 #[test]
208 fn can_format_diff_seconds() {
209 let start = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 11).unwrap());
210 let end = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 30).unwrap());
211 assert_eq!(readable_diff(start, end), Some("19 seconds".to_owned()));
212 }
213
214 #[test]
215 fn can_format_diff_minutes() {
216 let start = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 11).unwrap());
217 let end = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 15, 11).unwrap());
218 assert_eq!(readable_diff(start, end), Some("5 minutes".to_owned()));
219 }
220
221 #[test]
222 fn can_format_diff_hours() {
223 let start = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 11).unwrap());
224 let end = Ok(Local.with_ymd_and_hms(2020, 5, 20, 12, 10, 11).unwrap());
225 assert_eq!(readable_diff(start, end), Some("3 hours".to_owned()));
226 }
227
228 #[test]
229 fn can_format_diff_days() {
230 let start = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 11).unwrap());
231 let end = Ok(Local.with_ymd_and_hms(2020, 5, 30, 9, 10, 11).unwrap());
232 assert_eq!(readable_diff(start, end), Some("10 days".to_owned()));
233 }
234
235 #[test]
236 fn can_format_diff_minutes_seconds() {
237 let start = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 11).unwrap());
238 let end = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 15, 30).unwrap());
239 assert_eq!(
240 readable_diff(start, end),
241 Some("5 minutes, 19 seconds".to_owned())
242 );
243 }
244
245 #[test]
246 fn can_format_diff_days_minutes() {
247 let start = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 11).unwrap());
248 let end = Ok(Local.with_ymd_and_hms(2020, 5, 22, 9, 30, 11).unwrap());
249 assert_eq!(
250 readable_diff(start, end),
251 Some("2 days, 20 minutes".to_owned())
252 );
253 }
254
255 #[test]
256 fn can_format_diff_month() {
257 let start = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 11).unwrap());
258 let end = Ok(Local.with_ymd_and_hms(2020, 7, 20, 9, 10, 11).unwrap());
259 assert_eq!(readable_diff(start, end), Some("61 days".to_owned()));
260 }
261
262 #[test]
263 fn can_format_diff_year() {
264 let start = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 11).unwrap());
265 let end = Ok(Local.with_ymd_and_hms(2022, 7, 20, 9, 10, 11).unwrap());
266 assert_eq!(readable_diff(start, end), Some("791 days".to_owned()));
267 }
268
269 #[test]
270 fn can_format_diff_all() {
271 let start = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 11).unwrap());
272 let end = Ok(Local.with_ymd_and_hms(2020, 5, 22, 14, 32, 45).unwrap());
273 assert_eq!(
274 readable_diff(start, end),
275 Some("2 days, 5 hours, 22 minutes, 34 seconds".to_owned())
276 );
277 }
278
279 #[test]
280 fn can_format_no_diff() {
281 let start = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 11).unwrap());
282 let end = Ok(Local.with_ymd_and_hms(2020, 5, 20, 9, 10, 11).unwrap());
283 assert_eq!(readable_diff(start, end), Some(String::new()));
284 }
285}