Skip to main content

serde_duration_ext/
durationunit.rs

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
16/// The number of seconds in a minute.
17const SECS_PER_MINUTE: u64 = 60;
18/// The number of seconds in an hour.
19const SECS_PER_HOUR: u64 = 3600;
20/// The number of (non-leap) seconds in days.
21const SECS_PER_DAY: u64 = 86_400;
22/// The number of (non-leap) seconds in a week.
23const 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    /// Creates a new `DurationUnit` from the specified value and time unit.
53    pub fn new(value: u64, unit: TimeUnit) -> Self {
54        Self { value, unit }
55    }
56
57    /// Returns the time unit of this duration unit.
58    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}