openfare_lib/lock/plan/conditions/
current_time.rs1use 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 ¤t_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 let tz_offset = chrono::FixedOffset::east(0);
111 let time = chrono::NaiveTime::from_hms(0, 0, 0);
113 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}