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