iso8601_duration/
duration.rs

1use std::time::Duration as StdDuration;
2use std::{fmt, str::FromStr};
3
4use nom::{
5    branch::alt,
6    bytes::complete::tag,
7    character::complete::digit1,
8    combinator::{all_consuming, map_res, opt},
9    error::{ErrorKind, ParseError},
10    number::complete::float,
11    sequence::{preceded, separated_pair, terminated, tuple},
12    Err, Finish, IResult,
13};
14
15#[derive(Debug, PartialEq, Clone, Copy)]
16pub struct Duration {
17    pub year: f32,
18    pub month: f32,
19    pub day: f32,
20    pub hour: f32,
21    pub minute: f32,
22    pub second: f32,
23}
24
25impl Duration {
26    /// Create a new duration
27    pub fn new(year: f32, month: f32, day: f32, hour: f32, minute: f32, second: f32) -> Self {
28        Duration {
29            year,
30            month,
31            day,
32            hour,
33            minute,
34            second,
35        }
36    }
37
38    /// Return number of years in the duration
39    ///
40    /// This method will return `None` is `Duration` contains
41    /// `second`, `minute` or `hour`.
42    pub fn num_years(&self) -> Option<f32> {
43        if self.second > 0.0 || self.minute > 0.0 || self.hour > 0.0 {
44            return None;
45        }
46
47        Some(self.year + self.month / 12.)
48    }
49
50    /// Return number of months in the duration
51    ///
52    /// This method will return `None` is `Duration` contains
53    /// `second`, `minute` or `hour`.
54    pub fn num_months(&self) -> Option<f32> {
55        if self.second > 0.0 || self.minute > 0.0 || self.hour > 0.0 {
56            return None;
57        }
58
59        Some(self.year * 12. + self.month)
60    }
61
62    /// Return number of weeks in the duration
63    ///
64    /// This method will return `None` is `Duration` contains
65    /// `year` or `month`.
66    pub fn num_weeks(&self) -> Option<f32> {
67        if self.month > 0.0 || self.year > 0.0 {
68            return None;
69        }
70
71        Some(
72            self.second / 60. / 60. / 24. / 7.
73                + self.minute / 60. / 24. / 7.
74                + self.hour / 24. / 7.
75                + self.day / 7.,
76        )
77    }
78
79    /// Return number of days in the duration
80    ///
81    /// This method will return `None` is `Duration` contains
82    /// `year` or `month`.
83    pub fn num_days(&self) -> Option<f32> {
84        if self.month > 0.0 || self.year > 0.0 {
85            return None;
86        }
87
88        Some(self.second / 60. / 60. / 24. + self.minute / 60. / 24. + self.hour / 24. + self.day)
89    }
90
91    /// Return number of hours in the duration
92    ///
93    /// This method will return `None` is `Duration` contains
94    /// `year` or `month`.
95    pub fn num_hours(&self) -> Option<f32> {
96        if self.month > 0.0 || self.year > 0.0 {
97            return None;
98        }
99
100        Some(self.second / 60. / 60. + self.minute / 60. + self.hour + self.day * 24.)
101    }
102
103    /// Return number of minutes in the duration
104    ///
105    /// This method will return `None` is `Duration` contains
106    /// `year` or `month`.
107    pub fn num_minutes(&self) -> Option<f32> {
108        if self.month > 0.0 || self.year > 0.0 {
109            return None;
110        }
111
112        Some(self.second / 60. + self.minute + self.hour * 60. + self.day * 60. * 24.)
113    }
114
115    /// Return number of seconds in the duration
116    ///
117    /// This method will return `None` is `Duration` contains
118    /// `year` or `month`.
119    pub fn num_seconds(&self) -> Option<f32> {
120        if self.month > 0.0 || self.year > 0.0 {
121            return None;
122        }
123
124        Some(self.second + self.minute * 60. + self.hour * 60. * 60. + self.day * 60. * 60. * 24.)
125    }
126
127    /// Convert duration to `std::time::Duration`.
128    ///
129    /// This method will return `None` is `Duration` contains
130    /// `year` or `month`.
131    ///
132    /// See to know how to convert a `Duration` contains
133    /// `year` or `month`.
134    pub fn to_std(&self) -> Option<StdDuration> {
135        self.num_seconds().map(StdDuration::from_secs_f32)
136    }
137
138    /// Parse given string into Duration
139    pub fn parse(input: &str) -> Result<Duration, ParseDurationError> {
140        all_consuming(preceded(
141            tag("P"),
142            alt((parse_week_format, parse_basic_format)),
143        ))(input)
144        .finish()
145        .map(|(_, duration)| duration)
146        .map_err(|err| ParseDurationError::new(input, err))
147    }
148}
149
150#[derive(PartialEq, Eq)]
151pub struct ParseDurationError {
152    pub input: String,
153    pub position: usize,
154    pub kind: ErrorKind,
155}
156
157impl ParseDurationError {
158    fn new(input: &str, err: nom::error::Error<&str>) -> Self {
159        ParseDurationError {
160            input: input.to_string(),
161            position: input.len() - err.input.len(),
162            kind: err.code,
163        }
164    }
165}
166
167impl fmt::Debug for ParseDurationError {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
169        write!(
170            f,
171            "Parse error: {:?} in {:?} at position {}",
172            self.kind, self.input, self.position
173        )
174    }
175}
176
177impl FromStr for Duration {
178    type Err = ParseDurationError;
179    fn from_str(s: &str) -> Result<Self, Self::Err> {
180        Duration::parse(s)
181    }
182}
183
184impl fmt::Display for Duration {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        f.write_str("P")?;
187        if self.year > 0.0 {
188            write!(f, "{}Y", self.year)?;
189        }
190        if self.month > 0.0 {
191            write!(f, "{}M", self.month)?;
192        }
193        if self.day > 0.0 {
194            write!(f, "{}D", self.day)?;
195        }
196        if self.hour > 0.0 || self.minute > 0.0 || self.second > 0.0 {
197            f.write_str("T")?;
198        }
199        if self.hour > 0.0 {
200            write!(f, "{}H", self.hour)?;
201        }
202        if self.minute > 0.0 {
203            write!(f, "{}M", self.minute)?;
204        }
205        if self.second > 0.0 {
206            write!(f, "{}S", self.second)?;
207        }
208        Ok(())
209    }
210}
211
212fn decimal_comma_number(input: &str) -> IResult<&str, f32> {
213    map_res(separated_pair(digit1, tag(","), digit1), |(a, b)| {
214        f32::from_str(&format!("{}.{}", a, b))
215    })(input)
216}
217
218fn value_with_designator(designator: &str) -> impl Fn(&str) -> IResult<&str, f32> + '_ {
219    move |input| {
220        terminated(
221            alt((float, decimal_comma_number, map_res(digit1, f32::from_str))),
222            tag(designator),
223        )(input)
224    }
225}
226
227fn parse_basic_format(input: &str) -> IResult<&str, Duration> {
228    let (input, (year, month, day)) = tuple((
229        opt(value_with_designator("Y")),
230        opt(value_with_designator("M")),
231        opt(value_with_designator("D")),
232    ))(input)?;
233
234    let (input, time) = opt(preceded(
235        tag("T"),
236        tuple((
237            opt(value_with_designator("H")),
238            opt(value_with_designator("M")),
239            opt(value_with_designator("S")),
240        )),
241    ))(input)?;
242
243    let (hour, minute, second) = time.unwrap_or_default();
244
245    if year.is_none()
246        && month.is_none()
247        && day.is_none()
248        && hour.is_none()
249        && minute.is_none()
250        && second.is_none()
251    {
252        Err(Err::Error(ParseError::from_error_kind(
253            input,
254            ErrorKind::Verify,
255        )))
256    } else {
257        Ok((
258            input,
259            Duration {
260                year: year.unwrap_or_default(),
261                month: month.unwrap_or_default(),
262                day: day.unwrap_or_default(),
263                hour: hour.unwrap_or_default(),
264                minute: minute.unwrap_or_default(),
265                second: second.unwrap_or_default(),
266            },
267        ))
268    }
269}
270
271fn parse_week_format(input: &str) -> IResult<&str, Duration> {
272    let (input, week) = value_with_designator("W")(input)?;
273
274    Ok((
275        input,
276        Duration {
277            year: 0.,
278            month: 0.,
279            day: week * 7.,
280            hour: 0.,
281            minute: 0.,
282            second: 0.,
283        },
284    ))
285}
286
287fn _parse_extended_format(_input: &str) -> IResult<&str, Duration> {
288    unimplemented!()
289}