Skip to main content

duration_str/
unit.rs

1use crate::{
2    impl_expect_err, impl_expect_err_internal, CondUnit, DError, DResult, ExpectErr,
3    ONE_DAY_NANOSECOND, ONE_HOUR_NANOSECOND, ONE_MICROSECOND_NANOSECOND,
4    ONE_MILLISECOND_NANOSECOND, ONE_MINUTE_NANOSECOND, ONE_MONTH_NANOSECOND, ONE_SECOND_NANOSECOND,
5    ONE_WEEK_NANOSECOND, ONE_YEAR_NANOSECOND,
6};
7use std::fmt::{Debug, Display, Formatter};
8use std::str::FromStr;
9use winnow::ascii::multispace0;
10use winnow::combinator::{cut_err, eof, peek};
11use winnow::error::{ContextError, StrContext, StrContextValue};
12use winnow::stream::AsChar;
13use winnow::token::{one_of, take_while};
14use winnow::ModalResult as WResult;
15use winnow::Parser;
16
17#[derive(Debug, Eq, PartialEq, Default, Clone)]
18pub(crate) enum TimeUnit {
19    Year,
20    Month,
21    Week,
22    Day,
23    Hour,
24    Minute,
25    #[default]
26    Second,
27    MilliSecond,
28    MicroSecond,
29    NanoSecond,
30}
31
32impl Display for TimeUnit {
33    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
34        match self {
35            TimeUnit::Year => write!(f, "y"),
36            TimeUnit::Month => write!(f, "mon"),
37            TimeUnit::Week => write!(f, "w"),
38            TimeUnit::Day => write!(f, "d"),
39            TimeUnit::Hour => write!(f, "h"),
40            TimeUnit::Minute => write!(f, "min"),
41            TimeUnit::Second => write!(f, "s"),
42            TimeUnit::MilliSecond => write!(f, "ms"),
43            TimeUnit::MicroSecond => write!(f, "µs"),
44            TimeUnit::NanoSecond => write!(f, "ns"),
45        }
46    }
47}
48
49impl TimeUnit {
50    #[inline]
51    #[cfg(feature = "cn_unit")]
52    fn is_cn_unit(c: char) -> bool {
53        [
54            '年', '月', '周', '日', '天', '时', '分', '秒', '毫', '微', '纳',
55        ]
56        .contains(&c)
57    }
58
59    pub(crate) fn duration(&self, time_str: impl AsRef<str>) -> DResult<u64> {
60        let time = time_str
61            .as_ref()
62            .parse::<u64>()
63            .map_err(|err| DError::ParseError(err.to_string()))?;
64        let unit = match self {
65            TimeUnit::Year => ONE_YEAR_NANOSECOND,
66            TimeUnit::Month => ONE_MONTH_NANOSECOND,
67            TimeUnit::Week => ONE_WEEK_NANOSECOND,
68            TimeUnit::Day => ONE_DAY_NANOSECOND,
69            TimeUnit::Hour => ONE_HOUR_NANOSECOND,
70            TimeUnit::Minute => ONE_MINUTE_NANOSECOND,
71            TimeUnit::Second => ONE_SECOND_NANOSECOND,
72            TimeUnit::MilliSecond => ONE_MILLISECOND_NANOSECOND,
73            TimeUnit::MicroSecond => ONE_MICROSECOND_NANOSECOND,
74            TimeUnit::NanoSecond => 1,
75        };
76        time.checked_mul(unit).ok_or(DError::OverflowError)
77    }
78}
79
80impl FromStr for TimeUnit {
81    type Err = DError;
82
83    #[inline(always)]
84    fn from_str(s: &str) -> Result<Self, Self::Err> {
85        let owned;
86        let case = if cfg!(feature = "lowercase") {
87            s
88        } else {
89            owned = s.to_ascii_lowercase();
90            owned.as_str()
91        };
92
93        match case {
94            "y" | "year" | "years" => Ok(TimeUnit::Year),
95            "mon" | "month" | "months" => Ok(TimeUnit::Month),
96            "w" | "week" | "weeks" => Ok(TimeUnit::Week),
97            "d" | "day" | "days" => Ok(TimeUnit::Day),
98            "h" | "hr" | "hour" | "hours" => Ok(TimeUnit::Hour),
99            "m" | "min" | "minute" | "minutes" => Ok(TimeUnit::Minute),
100            "s" | "sec" | "second" | "seconds" => Ok(TimeUnit::Second),
101            "ms" | "msec" | "millisecond" | "milliseconds" => Ok(TimeUnit::MilliSecond),
102            "µs" | "µsec" | "µsecond" | "us" | "usec" | "usecond" | "microsecond"
103            | "microseconds" => Ok(TimeUnit::MicroSecond),
104            "ns" | "nsec" | "nanosecond" | "nanoseconds" => Ok(TimeUnit::NanoSecond),
105
106            #[cfg(feature = "cn_unit")]
107            "年" => Ok(TimeUnit::Year),
108            #[cfg(feature = "cn_unit")]
109            "月" => Ok(TimeUnit::Month),
110            #[cfg(feature = "cn_unit")]
111            "周" => Ok(TimeUnit::Week),
112            #[cfg(feature = "cn_unit")]
113            "日" | "天" => Ok(TimeUnit::Day),
114            #[cfg(feature = "cn_unit")]
115            "时" => Ok(TimeUnit::Hour),
116            #[cfg(feature = "cn_unit")]
117            "分" => Ok(TimeUnit::Minute),
118            #[cfg(feature = "cn_unit")]
119            "秒" => Ok(TimeUnit::Second),
120            #[cfg(feature = "cn_unit")]
121            "毫秒" => Ok(TimeUnit::MilliSecond),
122            #[cfg(feature = "cn_unit")]
123            "微秒" => Ok(TimeUnit::MicroSecond),
124            #[cfg(feature = "cn_unit")]
125            "纳秒" => Ok(TimeUnit::NanoSecond),
126            _ => Err(DError::ParseError(Self::expect_err(case))),
127        }
128    }
129}
130
131impl_expect_err!(
132    TimeUnit,
133    [&'static str; 11],
134    ["y", "mon", "w", "d", "h", "m", "s", "ms", "µs", "us", "ns"]
135);
136
137pub(crate) fn unit_abbr1(input: &mut &str) -> WResult<TimeUnit> {
138    let set = |c: char| c.is_alpha() || c == 'µ';
139    let set = {
140        #[cfg(feature = "cn_unit")]
141        {
142            move |c: char| set(c) || TimeUnit::is_cn_unit(c)
143        }
144        #[cfg(not(feature = "cn_unit"))]
145        set
146    };
147
148    take_while(1.., set)
149        .parse_to()
150        .context(StrContext::Expected(StrContextValue::Description(
151            TimeUnit::get_expect_val(),
152        )))
153        .parse_next(input)
154}
155
156pub(crate) fn opt_unit_abbr(input: &mut &str) -> WResult<TimeUnit> {
157    let result = unit_abbr1(input);
158    if result.is_err() {
159        multispace0.parse_next(input)?;
160        if eof::<_, ContextError>.parse_next(input).is_ok() {
161            // The input result is empty except for spaces. Give `TimeUnit` default value
162            return Ok(TimeUnit::default());
163        }
164
165        return cut_err(peek(one_of(CondUnit::contain)))
166            .context(StrContext::Expected(StrContextValue::Description(
167                TimeUnit::get_expect_val(),
168            )))
169            .value(TimeUnit::default())
170            .parse_next(input);
171    }
172    result
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178    use crate::catch_err;
179    use winnow::{Parser, Partial};
180
181    #[test]
182    fn test_time_unit_abbr() {
183        assert_eq!(
184            unit_abbr1.parse_peek(&Partial::new("y")),
185            Ok(("", TimeUnit::Year))
186        );
187        assert_eq!(
188            unit_abbr1.parse_peek(&Partial::new("mon")),
189            Ok(("", TimeUnit::Month))
190        );
191        assert_eq!(
192            unit_abbr1.parse_peek(&Partial::new("w")),
193            Ok(("", TimeUnit::Week))
194        );
195        assert_eq!(
196            unit_abbr1.parse_peek(&Partial::new("d")),
197            Ok(("", TimeUnit::Day))
198        );
199        assert_eq!(
200            unit_abbr1.parse_peek(&Partial::new("h")),
201            Ok(("", TimeUnit::Hour))
202        );
203        assert_eq!(
204            unit_abbr1.parse_peek(&Partial::new("m")),
205            Ok(("", TimeUnit::Minute))
206        );
207        assert_eq!(
208            unit_abbr1.parse_peek(&Partial::new("s")),
209            Ok(("", TimeUnit::Second))
210        );
211        assert_eq!(
212            unit_abbr1.parse_peek(&Partial::new("ms")),
213            Ok(("", TimeUnit::MilliSecond))
214        );
215        assert_eq!(
216            unit_abbr1.parse_peek(&Partial::new("µs")),
217            Ok(("", TimeUnit::MicroSecond))
218        );
219        assert_eq!(
220            unit_abbr1.parse_peek(&Partial::new("ns")),
221            Ok(("", TimeUnit::NanoSecond))
222        );
223    }
224
225    #[cfg(feature = "cn_unit")]
226    #[test]
227    fn test_time_cn_unit_abbr() {
228        assert_eq!(
229            unit_abbr1.parse_peek(&Partial::new("年")),
230            Ok(("", TimeUnit::Year))
231        );
232        assert_eq!(
233            unit_abbr1.parse_peek(&Partial::new("月")),
234            Ok(("", TimeUnit::Month))
235        );
236        assert_eq!(
237            unit_abbr1.parse_peek(&Partial::new("周")),
238            Ok(("", TimeUnit::Week))
239        );
240        assert_eq!(
241            unit_abbr1.parse_peek(&Partial::new("日")),
242            Ok(("", TimeUnit::Day))
243        );
244        assert_eq!(
245            unit_abbr1.parse_peek(&Partial::new("天")),
246            Ok(("", TimeUnit::Day))
247        );
248        assert_eq!(
249            unit_abbr1.parse_peek(&Partial::new("时")),
250            Ok(("", TimeUnit::Hour))
251        );
252        assert_eq!(
253            unit_abbr1.parse_peek(&Partial::new("分")),
254            Ok(("", TimeUnit::Minute))
255        );
256        assert_eq!(
257            unit_abbr1.parse_peek(&Partial::new("秒")),
258            Ok(("", TimeUnit::Second))
259        );
260        assert_eq!(
261            unit_abbr1.parse_peek(&Partial::new("毫秒")),
262            Ok(("", TimeUnit::MilliSecond))
263        );
264        assert_eq!(
265            unit_abbr1.parse_peek(&Partial::new("微秒")),
266            Ok(("", TimeUnit::MicroSecond))
267        );
268        assert_eq!(
269            unit_abbr1.parse_peek(&Partial::new("纳秒")),
270            Ok(("", TimeUnit::NanoSecond))
271        );
272    }
273
274    #[test]
275    fn test_time_unit() {
276        let (input, format) = unit_abbr1.parse_peek("m123").unwrap();
277        assert_eq!(input, "123");
278        assert_eq!(format, TimeUnit::Minute);
279    }
280
281    #[test]
282    fn test_unit_abbr1_err() {
283        let expect_err = r#"
284nys
285^
286expected ["y", "mon", "w", "d", "h", "m", "s", "ms", "µs", "us", "ns"]"#;
287        assert_eq!(
288            catch_err!(unit_abbr1.parse(&Partial::new("nys"))),
289            expect_err.trim_start()
290        );
291
292        let expect_err = r#"
293^
294expected ["y", "mon", "w", "d", "h", "m", "s", "ms", "µs", "us", "ns"]"#;
295        assert_eq!(catch_err!(unit_abbr1.parse(&Partial::new(""))), expect_err);
296    }
297
298    #[test]
299    fn test_opt_unit_abbr() {
300        assert_eq!(
301            opt_unit_abbr.parse_peek(&Partial::new("y")),
302            Ok(("", TimeUnit::Year))
303        );
304        assert_eq!(
305            opt_unit_abbr.parse_peek(&Partial::new("mon")),
306            Ok(("", TimeUnit::Month))
307        );
308        assert_eq!(
309            opt_unit_abbr.parse_peek(&Partial::new("w")),
310            Ok(("", TimeUnit::Week))
311        );
312        assert_eq!(
313            opt_unit_abbr.parse_peek(&Partial::new("d")),
314            Ok(("", TimeUnit::Day))
315        );
316        assert_eq!(
317            opt_unit_abbr.parse_peek(&Partial::new("h")),
318            Ok(("", TimeUnit::Hour))
319        );
320        assert_eq!(
321            opt_unit_abbr.parse_peek(&Partial::new("m")),
322            Ok(("", TimeUnit::Minute))
323        );
324        assert_eq!(
325            opt_unit_abbr.parse_peek(&Partial::new("s")),
326            Ok(("", TimeUnit::Second))
327        );
328        assert_eq!(
329            opt_unit_abbr.parse_peek(&Partial::new("ms")),
330            Ok(("", TimeUnit::MilliSecond))
331        );
332        assert_eq!(
333            opt_unit_abbr.parse_peek(&Partial::new("µs")),
334            Ok(("", TimeUnit::MicroSecond))
335        );
336        assert_eq!(
337            opt_unit_abbr.parse_peek(&Partial::new("ns")),
338            Ok(("", TimeUnit::NanoSecond))
339        );
340
341        assert_eq!(
342            opt_unit_abbr.parse_peek(&Partial::new("")),
343            Ok(("", TimeUnit::Second))
344        );
345
346        assert_eq!(
347            opt_unit_abbr.parse_peek(&Partial::new("        ")),
348            Ok(("", TimeUnit::Second))
349        );
350    }
351
352    #[test]
353    fn test_opt_unit_abbr_err() {
354        let expect_err = r#"
355nys
356^
357expected ["y", "mon", "w", "d", "h", "m", "s", "ms", "µs", "us", "ns"]"#;
358        assert_eq!(
359            catch_err!(opt_unit_abbr.parse(&Partial::new("nys"))),
360            expect_err.trim_start()
361        );
362    }
363}