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