gr/
time.rs

1// Time utility functions
2
3use crate::api_traits::Timestamp;
4use crate::remote::{ListBodyArgs, ListSortMode};
5use crate::Error;
6
7use crate::error::{self, GRError};
8use crate::Result;
9use chrono::{DateTime, Local};
10use std;
11use std::fmt::{Display, Formatter};
12use std::ops::{Add, AddAssign, Deref, Div, Sub};
13use std::str::FromStr;
14use std::time::Duration;
15
16enum Time {
17    Second,
18    Minute,
19    Hour,
20    Day,
21}
22
23impl Time {
24    fn to_seconds(&self) -> u64 {
25        match self {
26            Time::Second => 1,
27            Time::Minute => 60,
28            Time::Hour => 3600,
29            Time::Day => 86400,
30        }
31    }
32}
33
34impl TryFrom<char> for Time {
35    type Error = Error;
36
37    fn try_from(time: char) -> std::result::Result<Self, Self::Error> {
38        match time {
39            's' => Ok(Time::Second),
40            'm' => Ok(Time::Minute),
41            'h' => Ok(Time::Hour),
42            'd' => Ok(Time::Day),
43            _ => Err(error::gen(format!(
44                "Unknown char time format: {} - valid types are s, m, h, d",
45                time
46            ))),
47        }
48    }
49}
50
51pub fn now_epoch_seconds() -> Seconds {
52    let now_epoch = std::time::SystemTime::now()
53        .duration_since(std::time::UNIX_EPOCH)
54        .unwrap()
55        .as_secs();
56    Seconds(now_epoch)
57}
58
59pub fn epoch_to_minutes_relative(epoch_seconds: Seconds) -> String {
60    let now = now_epoch_seconds();
61    let diff = now - epoch_seconds;
62    let minutes = diff / Seconds::new(60);
63    minutes.to_string()
64}
65
66pub fn epoch_to_seconds_relative(epoch_seconds: Seconds) -> String {
67    let now = now_epoch_seconds();
68    let diff = now - epoch_seconds;
69    diff.to_string()
70}
71
72#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
73pub struct Seconds(u64);
74
75impl Seconds {
76    pub fn new(seconds: u64) -> Self {
77        Seconds(seconds)
78    }
79}
80
81impl FromStr for Seconds {
82    type Err = GRError;
83
84    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
85        match s.parse::<u64>() {
86            Ok(seconds) => Ok(Seconds(seconds)),
87            Err(err) => Err(GRError::TimeConversionError(format!(
88                "Could not convert {} to time format: {}",
89                s, err,
90            ))),
91        }
92    }
93}
94
95impl Sub<Seconds> for Seconds {
96    type Output = Seconds;
97
98    fn sub(self, rhs: Seconds) -> Self::Output {
99        if self.0 < rhs.0 {
100            return Seconds(rhs.0 - self.0);
101        }
102        Seconds(self.0 - rhs.0)
103    }
104}
105
106impl Add<Seconds> for Seconds {
107    type Output = Seconds;
108
109    fn add(self, rhs: Seconds) -> Self::Output {
110        Seconds(self.0 + rhs.0)
111    }
112}
113
114impl Div<Seconds> for Seconds {
115    type Output = Seconds;
116
117    fn div(self, rhs: Seconds) -> Self::Output {
118        Seconds(self.0 / rhs.0)
119    }
120}
121
122impl Deref for Seconds {
123    type Target = u64;
124
125    fn deref(&self) -> &Self::Target {
126        &self.0
127    }
128}
129
130impl From<u64> for Seconds {
131    fn from(seconds: u64) -> Self {
132        Seconds(seconds)
133    }
134}
135
136impl Display for Seconds {
137    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
138        write!(f, "{}", self.0)
139    }
140}
141
142#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
143pub struct Milliseconds(u64);
144
145impl Milliseconds {
146    pub fn new(milliseconds: u64) -> Self {
147        Milliseconds(milliseconds)
148    }
149}
150
151impl Deref for Milliseconds {
152    type Target = u64;
153
154    fn deref(&self) -> &Self::Target {
155        &self.0
156    }
157}
158
159impl From<u64> for Milliseconds {
160    fn from(milliseconds: u64) -> Self {
161        Milliseconds(milliseconds)
162    }
163}
164
165impl From<Milliseconds> for Duration {
166    fn from(milliseconds: Milliseconds) -> Self {
167        Duration::from_millis(milliseconds.0)
168    }
169}
170
171impl From<Seconds> for Milliseconds {
172    fn from(seconds: Seconds) -> Self {
173        Milliseconds(seconds.0 * 1000)
174    }
175}
176
177impl Add<Milliseconds> for Milliseconds {
178    type Output = Milliseconds;
179
180    fn add(self, rhs: Milliseconds) -> Self::Output {
181        Milliseconds(self.0 + rhs.0)
182    }
183}
184
185impl AddAssign<Milliseconds> for Milliseconds {
186    fn add_assign(&mut self, rhs: Milliseconds) {
187        self.0 += rhs.0;
188    }
189}
190
191impl Display for Milliseconds {
192    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
193        write!(f, "{}", self.0)
194    }
195}
196
197/// Convert a string with time format to seconds.
198/// A string with time format can be anything like:
199/// 1s, 2s, 2 seconds, 2 second, 2seconds, 2second, 2 s
200/// The same would apply for minutes, hours and days
201/// Processing stops at the first non-digit character
202fn string_to_seconds(str_fmt: &str) -> Result<Seconds> {
203    let mut seconds: u64 = 0;
204    for c in str_fmt.chars() {
205        if c.is_ascii_digit() {
206            seconds = seconds * 10 + c.to_digit(10).unwrap() as u64;
207        } else {
208            if c.is_whitespace() {
209                continue;
210            }
211            seconds *= Time::try_from(c)?.to_seconds();
212            break;
213        }
214    }
215    Ok(Seconds(seconds))
216}
217
218impl TryFrom<&str> for Seconds {
219    type Error = GRError;
220
221    fn try_from(str_fmt: &str) -> std::result::Result<Self, Self::Error> {
222        match string_to_seconds(str_fmt) {
223            Ok(seconds) => Ok(seconds),
224            Err(err) => Err(GRError::TimeConversionError(format!(
225                "Could not convert {} to time format: {}",
226                str_fmt, err,
227            ))),
228        }
229    }
230}
231
232pub fn sort_filter_by_date<T: Timestamp>(
233    data: Vec<T>,
234    list_args: Option<ListBodyArgs>,
235) -> Result<Vec<T>> {
236    if let Some(list_args) = list_args {
237        let (created_after, created_before) = (list_args.created_after, list_args.created_before);
238        match (created_after, created_before) {
239            (Some(created_after), Some(created_before)) => {
240                let created_after = created_after.parse::<DateTime<Local>>().map_err(|err| {
241                    GRError::TimeConversionError(format!(
242                        "Could not convert {} to date format: {}",
243                        created_after, err,
244                    ))
245                })?;
246                let created_before = created_before.parse::<DateTime<Local>>().map_err(|err| {
247                    GRError::TimeConversionError(format!(
248                        "Could not convert {} to date format: {}",
249                        created_before, err,
250                    ))
251                })?;
252                return Ok(sort_by_date(
253                    data,
254                    Some(created_after),
255                    Some(created_before),
256                    Some(list_args.sort_mode),
257                ));
258            }
259            (Some(created_after), None) => {
260                let created_after = created_after.parse::<DateTime<Local>>().map_err(|err| {
261                    GRError::TimeConversionError(format!(
262                        "Could not convert {} to date format: {}",
263                        created_after, err,
264                    ))
265                })?;
266                return Ok(sort_by_date(
267                    data,
268                    Some(created_after),
269                    None,
270                    Some(list_args.sort_mode),
271                ));
272            }
273            (None, Some(created_before)) => {
274                let created_before = created_before.parse::<DateTime<Local>>().map_err(|err| {
275                    GRError::TimeConversionError(format!(
276                        "Could not convert {} to date format: {}",
277                        created_before, err,
278                    ))
279                })?;
280                return Ok(sort_by_date(
281                    data,
282                    None,
283                    Some(created_before),
284                    Some(list_args.sort_mode),
285                ));
286            }
287            (None, None) => {
288                return Ok(sort_by_date(data, None, None, Some(list_args.sort_mode)));
289            }
290        }
291    }
292    Ok(sort_by_date(data, None, None, Some(ListSortMode::Asc)))
293}
294
295fn sort_by_date<T: Timestamp>(
296    data: Vec<T>,
297    created_after: Option<DateTime<Local>>,
298    created_before: Option<DateTime<Local>>,
299    sort_mode: Option<ListSortMode>,
300) -> Vec<T> {
301    let mut data_dates = match (created_after, created_before) {
302        (Some(created_after), Some(created_before)) => data
303            .into_iter()
304            .filter_map(|item| {
305                let item_date = item.created_at().parse::<DateTime<Local>>().ok()?;
306                if item_date >= created_after && item_date <= created_before {
307                    return Some((item, item_date));
308                }
309                None
310            })
311            .collect::<Vec<(T, DateTime<Local>)>>(),
312        (Some(created_after), None) => data
313            .into_iter()
314            .filter_map(|item| {
315                let item_date = item.created_at().parse::<DateTime<Local>>().ok()?;
316                if item_date >= created_after {
317                    return Some((item, item_date));
318                }
319                None
320            })
321            .collect::<Vec<(T, DateTime<Local>)>>(),
322        (None, Some(created_before)) => data
323            .into_iter()
324            .filter_map(|item| {
325                let item_date = item.created_at().parse::<DateTime<Local>>().ok()?;
326                if item_date <= created_before {
327                    return Some((item, item_date));
328                }
329                None
330            })
331            .collect::<Vec<(T, DateTime<Local>)>>(),
332        (None, None) => data
333            .into_iter()
334            .map(|item| {
335                let item_date = item.created_at().parse::<DateTime<Local>>().unwrap();
336                (item, item_date)
337            })
338            .collect::<Vec<(T, DateTime<Local>)>>(),
339    };
340    if let Some(sort_mode) = sort_mode {
341        match sort_mode {
342            ListSortMode::Asc => data_dates.sort_by(|a, b| a.1.cmp(&b.1)),
343            ListSortMode::Desc => data_dates.sort_by(|a, b| b.1.cmp(&a.1)),
344        }
345    }
346    data_dates.into_iter().map(|(item, _)| item).collect()
347}
348
349pub fn compute_duration(start: &str, end: &str) -> u64 {
350    let created_at = chrono::DateTime::parse_from_rfc3339(start).unwrap();
351    let updated_at = chrono::DateTime::parse_from_rfc3339(end).unwrap();
352    updated_at.signed_duration_since(created_at).num_seconds() as u64
353}
354
355#[cfg(test)]
356mod tests {
357    use super::*;
358
359    #[test]
360    fn test_time_formatted_string_to_seconds() {
361        let test_table = vec![
362            ("1s", Seconds(1)),
363            ("2s", Seconds(2)),
364            ("2 seconds", Seconds(2)),
365            ("2 second", Seconds(2)),
366            ("2seconds", Seconds(2)),
367            ("2second", Seconds(2)),
368            ("2 s", Seconds(2)),
369            ("1m", Seconds(60)),
370            ("2m", Seconds(120)),
371            ("2 minutes", Seconds(120)),
372            ("2 minute", Seconds(120)),
373            ("2minutes", Seconds(120)),
374            ("2minute", Seconds(120)),
375            ("2 m", Seconds(120)),
376            ("1h", Seconds(3600)),
377            ("2h", Seconds(7200)),
378            ("2 hours", Seconds(7200)),
379            ("2 hour", Seconds(7200)),
380            ("2hours", Seconds(7200)),
381            ("2hour", Seconds(7200)),
382            ("2 h", Seconds(7200)),
383            ("1d", Seconds(86400)),
384            ("2d", Seconds(172800)),
385            ("2 days", Seconds(172800)),
386            ("2 day", Seconds(172800)),
387            ("2days", Seconds(172800)),
388            ("2day", Seconds(172800)),
389            ("2 d", Seconds(172800)),
390            // If no time format is specified, it defaults to seconds
391            ("300", Seconds(300)),
392            // empty string is zero
393            ("", Seconds(0)),
394        ];
395        for (input, expected) in test_table {
396            let actual = string_to_seconds(input).unwrap();
397            assert_eq!(expected.0, actual.0);
398        }
399    }
400
401    #[test]
402    fn test_cannot_convert_time_formatted_string_to_seconds() {
403        let input_err = "2x"; // user meant 2d and typed 2x
404        assert!(string_to_seconds(input_err).is_err());
405    }
406
407    struct TimestampMock {
408        created_at: String,
409    }
410
411    impl TimestampMock {
412        fn new(created_at: &str) -> Self {
413            TimestampMock {
414                created_at: created_at.to_string(),
415            }
416        }
417    }
418
419    impl Timestamp for TimestampMock {
420        fn created_at(&self) -> String {
421            self.created_at.clone()
422        }
423    }
424
425    #[test]
426    fn test_filter_date_created_after_iso_8601() {
427        let created_after = "2021-01-01T00:00:00Z".to_string();
428        let list_args = ListBodyArgs::builder()
429            .created_after(Some(created_after))
430            .build()
431            .unwrap();
432        let data = vec![
433            TimestampMock::new("2021-01-01T00:00:00Z"),
434            TimestampMock::new("2020-12-31T00:00:00Z"),
435            TimestampMock::new("2021-03-02T00:00:00Z"),
436            TimestampMock::new("2021-02-02T00:00:00Z"),
437        ];
438        let filtered = sort_filter_by_date(data, Some(list_args)).unwrap();
439        assert_eq!(3, filtered.len());
440        assert_eq!("2021-01-01T00:00:00Z", filtered[0].created_at());
441        assert_eq!("2021-02-02T00:00:00Z", filtered[1].created_at());
442        assert_eq!("2021-03-02T00:00:00Z", filtered[2].created_at());
443    }
444
445    #[test]
446    fn test_filter_date_created_after_iso_8601_no_date() {
447        let data = vec![
448            TimestampMock::new("2021-01-01T00:00:00Z"),
449            TimestampMock::new("2020-12-31T00:00:00Z"),
450            TimestampMock::new("2021-01-02T00:00:00Z"),
451        ];
452        // no filter, just data sort ascending.
453        let sorted = sort_filter_by_date(data, None).unwrap();
454        assert_eq!(3, sorted.len());
455        assert_eq!("2020-12-31T00:00:00Z", sorted[0].created_at());
456        assert_eq!("2021-01-01T00:00:00Z", sorted[1].created_at());
457        assert_eq!("2021-01-02T00:00:00Z", sorted[2].created_at());
458    }
459
460    #[test]
461    fn test_filter_date_created_at_iso_8601_invalid_date_filtered_out() {
462        let created_after = "2021-01-01T00:00:00Z".to_string();
463        let list_args = ListBodyArgs::builder()
464            .created_after(Some(created_after))
465            .build()
466            .unwrap();
467        let data = vec![
468            TimestampMock::new("2021-01/01"),
469            TimestampMock::new("2020-12-31T00:00:00Z"),
470            TimestampMock::new("2021-01-02T00:00:00Z"),
471        ];
472        let filtered = sort_filter_by_date(data, Some(list_args)).unwrap();
473        assert_eq!(1, filtered.len());
474    }
475
476    #[test]
477    fn test_created_after_invalid_date_is_error() {
478        let created_after = "2021-01/01".to_string();
479        let list_args = ListBodyArgs::builder()
480            .created_after(Some(created_after))
481            .build()
482            .unwrap();
483        let data = vec![
484            TimestampMock::new("2021-01/01"),
485            TimestampMock::new("2020-12-31T00:00:00Z"),
486            TimestampMock::new("2021-01-02T00:00:00Z"),
487        ];
488        let result = sort_filter_by_date(data, Some(list_args));
489        match result {
490            Err(err) => match err.downcast_ref::<GRError>() {
491                Some(GRError::TimeConversionError(_)) => (),
492                _ => panic!("Expected TimeConversionError"),
493            },
494            _ => panic!("Expected TimeConversionError"),
495        }
496    }
497
498    #[test]
499    fn test_sort_by_date_descending_order() {
500        let data = vec![
501            TimestampMock::new("2021-01-01T00:00:00Z"),
502            TimestampMock::new("2020-12-31T00:00:00Z"),
503            TimestampMock::new("2021-01-02T00:00:00Z"),
504        ];
505        let sorted = sort_by_date(data, None, None, Some(ListSortMode::Desc));
506        assert_eq!(3, sorted.len());
507        assert_eq!("2021-01-02T00:00:00Z", sorted[0].created_at());
508        assert_eq!("2021-01-01T00:00:00Z", sorted[1].created_at());
509        assert_eq!("2020-12-31T00:00:00Z", sorted[2].created_at());
510    }
511
512    #[test]
513    fn test_filter_by_created_before_date() {
514        let created_before = "2021-01-01T00:00:00Z".to_string();
515        let list_args = ListBodyArgs::builder()
516            .created_before(Some(created_before))
517            .build()
518            .unwrap();
519        let data = vec![
520            TimestampMock::new("2021-01-01T00:00:00Z"),
521            TimestampMock::new("2020-12-31T00:00:00Z"),
522            TimestampMock::new("2021-03-02T00:00:00Z"),
523            TimestampMock::new("2021-02-02T00:00:00Z"),
524        ];
525        let filtered = sort_filter_by_date(data, Some(list_args)).unwrap();
526        assert_eq!(2, filtered.len());
527        assert_eq!("2020-12-31T00:00:00Z", filtered[0].created_at());
528        assert_eq!("2021-01-01T00:00:00Z", filtered[1].created_at());
529    }
530
531    #[test]
532    fn test_filter_by_created_after_and_created_before_date() {
533        let created_after = "2021-01-01T00:00:00Z".to_string();
534        let created_before = "2021-02-01T00:00:00Z".to_string();
535        let list_args = ListBodyArgs::builder()
536            .created_after(Some(created_after))
537            .created_before(Some(created_before))
538            .build()
539            .unwrap();
540        let data = vec![
541            TimestampMock::new("2021-01-01T00:00:00Z"),
542            TimestampMock::new("2021-01-20T00:00:00Z"),
543            TimestampMock::new("2020-12-31T00:00:00Z"),
544            TimestampMock::new("2021-03-02T00:00:00Z"),
545            TimestampMock::new("2021-02-02T00:00:00Z"),
546        ];
547        let filtered = sort_filter_by_date(data, Some(list_args)).unwrap();
548        assert_eq!(2, filtered.len());
549        assert_eq!("2021-01-01T00:00:00Z", filtered[0].created_at());
550        assert_eq!("2021-01-20T00:00:00Z", filtered[1].created_at());
551    }
552
553    #[test]
554    fn test_no_filter_with_no_created_after_and_no_created_before() {
555        let data = vec![
556            TimestampMock::new("2021-01-01T00:00:00Z"),
557            TimestampMock::new("2020-12-31T00:00:00Z"),
558            TimestampMock::new("2021-03-02T00:00:00Z"),
559            TimestampMock::new("2021-02-02T00:00:00Z"),
560        ];
561        let filtered = sort_filter_by_date(data, None).unwrap();
562        assert_eq!(4, filtered.len());
563        assert_eq!("2020-12-31T00:00:00Z", filtered[0].created_at());
564        assert_eq!("2021-01-01T00:00:00Z", filtered[1].created_at());
565        assert_eq!("2021-02-02T00:00:00Z", filtered[2].created_at());
566        assert_eq!("2021-03-02T00:00:00Z", filtered[3].created_at());
567    }
568
569    #[test]
570    fn test_error_if_created_before_invalid_non_iso_8601_date() {
571        let created_before = "2021-01/01".to_string();
572        let list_args = ListBodyArgs::builder()
573            .created_before(Some(created_before))
574            .build()
575            .unwrap();
576        let data = vec![
577            TimestampMock::new("2020-12-31T00:00:00Z"),
578            TimestampMock::new("2021-01-02T00:00:00Z"),
579        ];
580        let result = sort_filter_by_date(data, Some(list_args));
581        match result {
582            Err(err) => match err.downcast_ref::<GRError>() {
583                Some(GRError::TimeConversionError(_)) => (),
584                _ => panic!("Expected TimeConversionError"),
585            },
586            _ => panic!("Expected TimeConversionError"),
587        }
588    }
589
590    #[test]
591    fn test_created_after_and_before_available_after_is_invalid_date() {
592        let created_after = "2021-01/01".to_string();
593        let created_before = "2021-01-01T00:00:00Z".to_string();
594        let list_args = ListBodyArgs::builder()
595            .created_after(Some(created_after))
596            .created_before(Some(created_before))
597            .build()
598            .unwrap();
599        let data = vec![
600            TimestampMock::new("2020-12-31T00:00:00Z"),
601            TimestampMock::new("2021-01-02T00:00:00Z"),
602        ];
603        let result = sort_filter_by_date(data, Some(list_args));
604        match result {
605            Err(err) => match err.downcast_ref::<GRError>() {
606                Some(GRError::TimeConversionError(_)) => (),
607                _ => panic!("Expected TimeConversionError"),
608            },
609            _ => panic!("Expected TimeConversionError"),
610        }
611    }
612
613    #[test]
614    fn test_created_after_and_before_available_before_is_invalid_date() {
615        let created_after = "2021-01-01T00:00:00Z".to_string();
616        let created_before = "2021-01/01".to_string();
617        let list_args = ListBodyArgs::builder()
618            .created_after(Some(created_after))
619            .created_before(Some(created_before))
620            .build()
621            .unwrap();
622        let data = vec![
623            TimestampMock::new("2020-12-31T00:00:00Z"),
624            TimestampMock::new("2021-01-02T00:00:00Z"),
625        ];
626        let result = sort_filter_by_date(data, Some(list_args));
627        match result {
628            Err(err) => match err.downcast_ref::<GRError>() {
629                Some(GRError::TimeConversionError(_)) => (),
630                _ => panic!("Expected TimeConversionError"),
631            },
632            _ => panic!("Expected TimeConversionError"),
633        }
634    }
635
636    #[test]
637    fn test_compute_duration() {
638        let created_at = "2020-01-01T00:00:00Z";
639        let updated_at = "2020-01-01T00:01:00Z";
640        let duration = compute_duration(created_at, updated_at);
641        assert_eq!(60, duration);
642    }
643}