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 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 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 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 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 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 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 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 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 pub fn to_std(&self) -> Option<StdDuration> {
135 self.num_seconds().map(StdDuration::from_secs_f32)
136 }
137
138 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}