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