openfare_lib/lock/plan/conditions/
current_time.rs

1use super::common;
2use anyhow::{format_err, Result};
3use lazy_regex::regex;
4use std::convert::TryFrom;
5
6use chrono::{TimeZone, Utc};
7
8#[derive(Debug, Clone, Eq, PartialEq)]
9pub struct CurrentTime {
10    operator: common::Operator,
11    time: chrono::DateTime<Utc>,
12}
13
14impl std::convert::TryFrom<&str> for CurrentTime {
15    type Error = anyhow::Error;
16    fn try_from(value: &str) -> Result<Self, Self::Error> {
17        let (operator, time) = parse_value(&value)?;
18        Ok(Self { operator, time })
19    }
20}
21
22impl CurrentTime {
23    pub fn evaluate(&self) -> Result<bool> {
24        let current_time = chrono::offset::Utc::now();
25        let result = common::evaluate_operator::<chrono::DateTime<Utc>>(
26            &current_time,
27            &self.operator,
28            &self.time,
29        );
30        Ok(result)
31    }
32}
33
34impl serde::Serialize for CurrentTime {
35    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
36    where
37        S: serde::Serializer,
38    {
39        serializer.serialize_str(
40            format!(
41                "{} {}",
42                self.operator.to_string(),
43                self.time.format("%Y-%m-%d"),
44            )
45            .as_str(),
46        )
47    }
48}
49
50struct Visitor {
51    marker: std::marker::PhantomData<fn() -> CurrentTime>,
52}
53
54impl Visitor {
55    fn new() -> Self {
56        Visitor {
57            marker: std::marker::PhantomData,
58        }
59    }
60}
61
62impl<'de> serde::de::Visitor<'de> for Visitor {
63    type Value = CurrentTime;
64
65    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
66        formatter.write_str("a string such as '< 2022-01-31'")
67    }
68
69    fn visit_str<E>(self, value: &str) -> core::result::Result<Self::Value, E>
70    where
71        E: serde::de::Error,
72    {
73        let (operator, time) = parse_value(&value).expect("parse current-time condition value");
74        Ok(Self::Value { operator, time })
75    }
76}
77
78impl<'de> serde::Deserialize<'de> for CurrentTime {
79    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
80    where
81        D: serde::Deserializer<'de>,
82    {
83        deserializer.deserialize_str(Visitor::new())
84    }
85}
86
87fn parse_value(value: &str) -> Result<(common::Operator, chrono::DateTime<Utc>)> {
88    let re = regex!(r"(?P<operator>(>=)|(<=)|(<)|(>)|(=))\s*(?P<time>.*)");
89    let captures = re
90        .captures(value)
91        .ok_or(format_err!("Regex failed to capture field."))?;
92
93    let operator_match = captures
94        .name("operator")
95        .expect("extract operator from regex capture")
96        .as_str();
97    let operator = common::Operator::try_from(operator_match)?;
98
99    let time_match = captures
100        .name("time")
101        .expect("extract time from regex capture")
102        .as_str();
103    let date = chrono::NaiveDate::parse_from_str(&time_match, "%Y-%m-%d")?;
104    let time = naive_date_to_utc(&date)?;
105    Ok((operator, time))
106}
107
108fn naive_date_to_utc(date: &chrono::NaiveDate) -> Result<chrono::DateTime<Utc>> {
109    // The known 1 hour time offset in seconds
110    let tz_offset = chrono::FixedOffset::east(0);
111    // The known time
112    let time = chrono::NaiveTime::from_hms(0, 0, 0);
113    // Naive date time, with no time zone information
114    let datetime = chrono::NaiveDateTime::new(date.clone(), time);
115
116    let dt_with_tz: chrono::DateTime<chrono::FixedOffset> =
117        tz_offset.from_local_datetime(&datetime).unwrap();
118    let dt_with_tz_utc: chrono::DateTime<Utc> = Utc.from_utc_datetime(&dt_with_tz.naive_utc());
119    Ok(dt_with_tz_utc)
120}
121
122#[test]
123fn test_evaluate_cases() -> Result<()> {
124    let condition = CurrentTime::try_from("< 3022-01-31")?;
125    assert!(condition.evaluate()?);
126    Ok(())
127}
128
129#[test]
130fn test_parse_str() -> Result<()> {
131    let result = CurrentTime::try_from("< 2022-01-31")?;
132
133    let expected_date = chrono::NaiveDate::parse_from_str("2022-01-31", "%Y-%m-%d")?;
134    let expected = CurrentTime {
135        operator: common::Operator::LessThan,
136        time: naive_date_to_utc(&expected_date)?,
137    };
138
139    assert_eq!(result, expected);
140    Ok(())
141}