1use std::str::FromStr;
2
3use lazy_static::lazy_static;
4use regex::Regex;
5use serde::{Deserialize, Deserializer};
6
7use crate::error::Error;
8use crate::TimeUnit;
9
10lazy_static! {
11 static ref DURATION_REGEX: Regex =
12 Regex::new(r"^(?P<value>\d+)(?P<unit>ns|us|ms|s|m|h|d|w){1}$")
13 .expect("Regex compilation error");
14}
15
16const SECS_PER_MINUTE: u64 = 60;
18const SECS_PER_HOUR: u64 = 3600;
20const SECS_PER_DAY: u64 = 86_400;
22const SECS_PER_WEEK: u64 = 604_800;
24
25#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
26pub struct DurationUnit {
27 value: u64,
28 unit: TimeUnit,
29}
30
31impl FromStr for DurationUnit {
32 type Err = Error;
33
34 fn from_str(s: &str) -> Result<Self, Self::Err> {
35 if DURATION_REGEX.is_match(s) {
36 let caps = DURATION_REGEX
37 .captures(s)
38 .ok_or_else(|| Error::StringDoesNotMatchRegex)?;
39 let value = caps.name("value").unwrap().as_str().parse().unwrap();
40 let time_unit = caps.name("unit").unwrap().as_str();
41 let unit = time_unit.parse::<TimeUnit>()?;
42 Ok(Self { value, unit })
43 } else {
44 Err(Error::Syntax(
45 "Current string is not correct duration unit value".to_owned(),
46 ))
47 }
48 }
49}
50
51impl DurationUnit {
52 pub fn new(value: u64, unit: TimeUnit) -> Self {
54 Self { value, unit }
55 }
56
57 pub fn unit(&self) -> TimeUnit {
59 self.unit
60 }
61
62 pub fn as_secs(&self) -> u64 {
63 match self.unit {
64 TimeUnit::Nanosecond => self.value / 1_000_000_000,
65 TimeUnit::Microsecond => self.value / 1_000_000,
66 TimeUnit::Millisecond => self.value / 1_000,
67 TimeUnit::Second => self.value,
68 TimeUnit::Minute => self.value * SECS_PER_MINUTE,
69 TimeUnit::Hour => self.value * SECS_PER_HOUR,
70 TimeUnit::Day => self.value * SECS_PER_DAY,
71 TimeUnit::Week => self.value * SECS_PER_WEEK,
72 }
73 }
74}
75
76impl From<DurationUnit> for std::time::Duration {
77 fn from(duration_unit: DurationUnit) -> Self {
78 match duration_unit.unit {
79 TimeUnit::Nanosecond => std::time::Duration::from_nanos(duration_unit.value),
80 TimeUnit::Microsecond => std::time::Duration::from_micros(duration_unit.value),
81 TimeUnit::Millisecond => std::time::Duration::from_millis(duration_unit.value),
82 TimeUnit::Second => std::time::Duration::from_secs(duration_unit.value),
83 TimeUnit::Minute => {
84 std::time::Duration::from_secs(duration_unit.value * SECS_PER_MINUTE)
85 }
86 TimeUnit::Hour => std::time::Duration::from_secs(duration_unit.value * SECS_PER_HOUR),
87 TimeUnit::Day => std::time::Duration::from_secs(duration_unit.value * SECS_PER_DAY),
88 TimeUnit::Week => std::time::Duration::from_secs(duration_unit.value * SECS_PER_WEEK),
89 }
90 }
91}
92
93#[cfg(feature = "chrono")]
94impl From<DurationUnit> for chrono::Duration {
95 fn from(duration_unit: DurationUnit) -> Self {
96 match duration_unit.unit {
97 TimeUnit::Nanosecond => chrono::Duration::nanoseconds(duration_unit.value as i64),
98 TimeUnit::Microsecond => chrono::Duration::microseconds(duration_unit.value as i64),
99 TimeUnit::Millisecond => chrono::Duration::milliseconds(duration_unit.value as i64),
100 TimeUnit::Second => chrono::Duration::seconds(duration_unit.value as i64),
101 TimeUnit::Minute => chrono::Duration::minutes(duration_unit.value as i64),
102 TimeUnit::Hour => chrono::Duration::hours(duration_unit.value as i64),
103 TimeUnit::Day => chrono::Duration::days(duration_unit.value as i64),
104 TimeUnit::Week => chrono::Duration::weeks(duration_unit.value as i64),
105 }
106 }
107}
108
109impl<'a> Deserialize<'a> for DurationUnit {
110 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
111 where
112 D: Deserializer<'a>,
113 {
114 String::deserialize(deserializer)?
115 .parse()
116 .map_err(serde::de::Error::custom)
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use std::time::Duration;
124
125 #[test]
126 fn test_duration_unit_from_str() {
127 let duration_unit = "10s".parse::<DurationUnit>().unwrap();
128 assert_eq!(duration_unit.value, 10);
129 assert_eq!(duration_unit.unit, TimeUnit::Second);
130
131 let duration_unit = "500ms".parse::<DurationUnit>().unwrap();
132 assert_eq!(duration_unit.value, 500);
133 assert_eq!(duration_unit.unit, TimeUnit::Millisecond);
134
135 let duration_unit = "1h".parse::<DurationUnit>().unwrap();
136 assert_eq!(duration_unit.value, 1);
137 assert_eq!(duration_unit.unit, TimeUnit::Hour);
138
139 let duration_unit = "100us".parse::<DurationUnit>().unwrap();
140 assert_eq!(duration_unit.value, 100);
141 assert_eq!(duration_unit.unit, TimeUnit::Microsecond);
142
143 let duration_unit = "2d".parse::<DurationUnit>().unwrap();
144 assert_eq!(duration_unit.value, 2);
145 assert_eq!(duration_unit.unit, TimeUnit::Day);
146
147 let duration_unit = "1w".parse::<DurationUnit>().unwrap();
148 assert_eq!(duration_unit.value, 1);
149 assert_eq!(duration_unit.unit, TimeUnit::Week);
150
151 let duration_unit = "invalid".parse::<DurationUnit>();
152 assert!(duration_unit.is_err());
153 }
154
155 #[test]
156 fn test_duration_unit_into_duration() {
157 let duration_unit = DurationUnit {
158 value: 10,
159 unit: TimeUnit::Second,
160 };
161 let duration: Duration = duration_unit.into();
162 assert_eq!(duration, Duration::from_secs(10));
163
164 let duration_unit = DurationUnit {
165 value: 500,
166 unit: TimeUnit::Millisecond,
167 };
168 let duration: Duration = duration_unit.into();
169 assert_eq!(duration, Duration::from_millis(500));
170
171 let duration_unit = DurationUnit {
172 value: 1,
173 unit: TimeUnit::Hour,
174 };
175 let duration: Duration = duration_unit.into();
176 assert_eq!(duration, Duration::from_secs(3600));
177
178 let duration_unit = DurationUnit {
179 value: 100,
180 unit: TimeUnit::Microsecond,
181 };
182 let duration: Duration = duration_unit.into();
183 assert_eq!(duration, Duration::from_micros(100));
184
185 let duration_unit = DurationUnit {
186 value: 2,
187 unit: TimeUnit::Day,
188 };
189 let duration: Duration = duration_unit.into();
190 assert_eq!(duration, Duration::from_secs(172_800));
191
192 let duration_unit = DurationUnit {
193 value: 1,
194 unit: TimeUnit::Week,
195 };
196 let duration: Duration = duration_unit.into();
197 assert_eq!(duration, Duration::from_secs(604_800));
198 }
199
200 #[cfg(feature = "chrono")]
201 #[test]
202 fn test_duration_unit_into_chrono_duration() {
203 let duration_unit = DurationUnit {
204 value: 10,
205 unit: TimeUnit::Second,
206 };
207 let duration: chrono::Duration = duration_unit.into();
208 assert_eq!(duration, chrono::Duration::seconds(10));
209
210 let duration_unit = DurationUnit {
211 value: 500,
212 unit: TimeUnit::Millisecond,
213 };
214 let duration: chrono::Duration = duration_unit.into();
215 assert_eq!(duration, chrono::Duration::milliseconds(500));
216
217 let duration_unit = DurationUnit {
218 value: 1,
219 unit: TimeUnit::Hour,
220 };
221 let duration: chrono::Duration = duration_unit.into();
222 assert_eq!(duration, chrono::Duration::hours(1));
223
224 let duration_unit = DurationUnit {
225 value: 100,
226 unit: TimeUnit::Microsecond,
227 };
228 let duration: chrono::Duration = duration_unit.into();
229 assert_eq!(duration, chrono::Duration::microseconds(100));
230
231 let duration_unit = DurationUnit {
232 value: 2,
233 unit: TimeUnit::Day,
234 };
235 let duration: chrono::Duration = duration_unit.into();
236 assert_eq!(duration, chrono::Duration::days(2));
237
238 let duration_unit = DurationUnit {
239 value: 1,
240 unit: TimeUnit::Week,
241 };
242 let duration: chrono::Duration = duration_unit.into();
243 assert_eq!(duration, chrono::Duration::weeks(1));
244 }
245
246 #[test]
247 fn test_deseialize_duration_unit() {
248 let duration_unit = serde_json::from_str::<DurationUnit>("\"10s\"").unwrap();
249 assert_eq!(duration_unit.value, 10);
250 assert_eq!(duration_unit.unit, TimeUnit::Second);
251
252 let duration_unit = serde_json::from_str::<DurationUnit>("\"500ms\"").unwrap();
253 assert_eq!(duration_unit.value, 500);
254 assert_eq!(duration_unit.unit, TimeUnit::Millisecond);
255
256 let duration_unit = serde_json::from_str::<DurationUnit>("\"1h\"").unwrap();
257 assert_eq!(duration_unit.value, 1);
258 assert_eq!(duration_unit.unit, TimeUnit::Hour);
259
260 let duration_unit = serde_json::from_str::<DurationUnit>("\"100us\"").unwrap();
261 assert_eq!(duration_unit.value, 100);
262 assert_eq!(duration_unit.unit, TimeUnit::Microsecond);
263
264 let duration_unit = serde_json::from_str::<DurationUnit>("\"2d\"").unwrap();
265 assert_eq!(duration_unit.value, 2);
266 assert_eq!(duration_unit.unit, TimeUnit::Day);
267
268 let duration_unit = serde_json::from_str::<DurationUnit>("\"1w\"").unwrap();
269 assert_eq!(duration_unit.value, 1);
270 assert_eq!(duration_unit.unit, TimeUnit::Week);
271
272 let duration_unit = serde_json::from_str::<DurationUnit>("\"invalid\"");
273 assert!(duration_unit.is_err());
274 }
275}