fd_lib/filter/
time.rs

1use chrono::{DateTime, Local, NaiveDate, NaiveDateTime};
2
3use std::time::SystemTime;
4
5/// Filter based on time ranges.
6#[derive(Debug, PartialEq, Eq)]
7pub enum TimeFilter {
8    Before(SystemTime),
9    After(SystemTime),
10}
11
12impl TimeFilter {
13    fn from_str(ref_time: &SystemTime, s: &str) -> Option<SystemTime> {
14        humantime::parse_duration(s)
15            .map(|duration| *ref_time - duration)
16            .ok()
17            .or_else(|| {
18                DateTime::parse_from_rfc3339(s)
19                    .map(|dt| dt.into())
20                    .ok()
21                    .or_else(|| {
22                        NaiveDate::parse_from_str(s, "%F")
23                            .ok()?
24                            .and_hms_opt(0, 0, 0)?
25                            .and_local_timezone(Local)
26                            .latest()
27                    })
28                    .or_else(|| {
29                        NaiveDateTime::parse_from_str(s, "%F %T")
30                            .ok()?
31                            .and_local_timezone(Local)
32                            .latest()
33                    })
34                    .or_else(|| {
35                        let timestamp_secs = s.strip_prefix('@')?.parse().ok()?;
36                        DateTime::from_timestamp(timestamp_secs, 0).map(Into::into)
37                    })
38                    .map(|dt| dt.into())
39            })
40    }
41
42    pub fn before(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
43        TimeFilter::from_str(ref_time, s).map(TimeFilter::Before)
44    }
45
46    pub fn after(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
47        TimeFilter::from_str(ref_time, s).map(TimeFilter::After)
48    }
49
50    pub fn applies_to(&self, t: &SystemTime) -> bool {
51        match self {
52            TimeFilter::Before(limit) => t < limit,
53            TimeFilter::After(limit) => t > limit,
54        }
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use std::time::Duration;
62
63    #[test]
64    fn is_time_filter_applicable() {
65        let ref_time = NaiveDateTime::parse_from_str("2010-10-10 10:10:10", "%F %T")
66            .unwrap()
67            .and_local_timezone(Local)
68            .latest()
69            .unwrap()
70            .into();
71
72        assert!(TimeFilter::after(&ref_time, "1min")
73            .unwrap()
74            .applies_to(&ref_time));
75        assert!(!TimeFilter::before(&ref_time, "1min")
76            .unwrap()
77            .applies_to(&ref_time));
78
79        let t1m_ago = ref_time - Duration::from_secs(60);
80        assert!(!TimeFilter::after(&ref_time, "30sec")
81            .unwrap()
82            .applies_to(&t1m_ago));
83        assert!(TimeFilter::after(&ref_time, "2min")
84            .unwrap()
85            .applies_to(&t1m_ago));
86
87        assert!(TimeFilter::before(&ref_time, "30sec")
88            .unwrap()
89            .applies_to(&t1m_ago));
90        assert!(!TimeFilter::before(&ref_time, "2min")
91            .unwrap()
92            .applies_to(&t1m_ago));
93
94        let t10s_before = "2010-10-10 10:10:00";
95        assert!(!TimeFilter::before(&ref_time, t10s_before)
96            .unwrap()
97            .applies_to(&ref_time));
98        assert!(TimeFilter::before(&ref_time, t10s_before)
99            .unwrap()
100            .applies_to(&t1m_ago));
101
102        assert!(TimeFilter::after(&ref_time, t10s_before)
103            .unwrap()
104            .applies_to(&ref_time));
105        assert!(!TimeFilter::after(&ref_time, t10s_before)
106            .unwrap()
107            .applies_to(&t1m_ago));
108
109        let same_day = "2010-10-10";
110        assert!(!TimeFilter::before(&ref_time, same_day)
111            .unwrap()
112            .applies_to(&ref_time));
113        assert!(!TimeFilter::before(&ref_time, same_day)
114            .unwrap()
115            .applies_to(&t1m_ago));
116
117        assert!(TimeFilter::after(&ref_time, same_day)
118            .unwrap()
119            .applies_to(&ref_time));
120        assert!(TimeFilter::after(&ref_time, same_day)
121            .unwrap()
122            .applies_to(&t1m_ago));
123
124        let ref_time = DateTime::parse_from_rfc3339("2010-10-10T10:10:10+00:00")
125            .unwrap()
126            .into();
127        let t1m_ago = ref_time - Duration::from_secs(60);
128        let t10s_before = "2010-10-10T10:10:00+00:00";
129        assert!(!TimeFilter::before(&ref_time, t10s_before)
130            .unwrap()
131            .applies_to(&ref_time));
132        assert!(TimeFilter::before(&ref_time, t10s_before)
133            .unwrap()
134            .applies_to(&t1m_ago));
135
136        assert!(TimeFilter::after(&ref_time, t10s_before)
137            .unwrap()
138            .applies_to(&ref_time));
139        assert!(!TimeFilter::after(&ref_time, t10s_before)
140            .unwrap()
141            .applies_to(&t1m_ago));
142
143        let ref_timestamp = 1707723412u64; // Mon Feb 12 07:36:52 UTC 2024
144        let ref_time = DateTime::parse_from_rfc3339("2024-02-12T07:36:52+00:00")
145            .unwrap()
146            .into();
147        let t1m_ago = ref_time - Duration::from_secs(60);
148        let t1s_later = ref_time + Duration::from_secs(1);
149        // Timestamp only supported via '@' prefix
150        assert!(TimeFilter::before(&ref_time, &ref_timestamp.to_string()).is_none());
151        assert!(
152            TimeFilter::before(&ref_time, &format!("@{}", ref_timestamp))
153                .unwrap()
154                .applies_to(&t1m_ago)
155        );
156        assert!(
157            !TimeFilter::before(&ref_time, &format!("@{}", ref_timestamp))
158                .unwrap()
159                .applies_to(&t1s_later)
160        );
161        assert!(
162            !TimeFilter::after(&ref_time, &format!("@{}", ref_timestamp))
163                .unwrap()
164                .applies_to(&t1m_ago)
165        );
166        assert!(TimeFilter::after(&ref_time, &format!("@{}", ref_timestamp))
167            .unwrap()
168            .applies_to(&t1s_later));
169    }
170}