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