xsd_types/lexical/duration/
mod.rs

1use super::{date_time::parse_seconds_decimal, Lexical, LexicalFormOf};
2use static_regular_grammar::RegularGrammar;
3
4pub mod day_time_duration;
5pub use day_time_duration::{DayTimeDuration, DayTimeDurationBuf, InvalidDayTimeDuration};
6
7pub mod year_month_duration;
8pub use year_month_duration::{InvalidYearMonthDuration, YearMonthDuration, YearMonthDurationBuf};
9
10/// Duration.
11///
12/// ```abnf
13/// duration = [ "-" ] %s"P" ((year-month [ day-time ]) / day-time)
14///
15/// year-month = (year [ month ]) / month
16///
17/// year = 1*DIGIT %s"Y"
18///
19/// month = 1*DIGIT %s"M"
20///
21/// day-time = (day [ time ]) / time
22///
23/// day = 1*DIGIT %s"D"
24///
25/// time = %s"T" ((hour [ minute ] [ second ]) / (minute [ second ]) / second)
26///
27/// hour = 1*DIGIT %s"H"
28///
29/// minute = 1*DIGIT %s"M"
30///
31/// second = ((1*DIGIT ["." *DIGIT] ) / "." 1*DIGIT) %s"S"
32/// ```
33#[derive(RegularGrammar, PartialEq, Eq, PartialOrd, Ord, Hash)]
34#[grammar(sized(DurationBuf, derive(PartialEq, Eq, PartialOrd, Ord, Hash)))]
35pub struct Duration(str);
36
37impl Duration {
38	pub fn parts(&self) -> Parts {
39		enum State {
40			Sign,
41			DateNumber,
42			TimeNumber,
43		}
44
45		let mut state = State::Sign;
46		let mut result = Parts {
47			is_negative: false,
48			year: None,
49			month: None,
50			day: None,
51			hour: None,
52			minute: None,
53			second: None,
54		};
55
56		let mut start = 0;
57
58		for (i, c) in self.0.char_indices() {
59			state = match state {
60				State::Sign => match c {
61					'-' => {
62						result.is_negative = true;
63						State::Sign
64					}
65					'P' => {
66						start = i + 1;
67						State::DateNumber
68					}
69					_ => unreachable!(),
70				},
71				State::DateNumber => match c {
72					'Y' => {
73						result.year = Some(&self.0[start..i]);
74						start = i + 1;
75						State::DateNumber
76					}
77					'M' => {
78						result.month = Some(&self.0[start..i]);
79						start = i + 1;
80						State::DateNumber
81					}
82					'D' => {
83						result.day = Some(&self.0[start..i]);
84						start = i + 1;
85						State::DateNumber
86					}
87					'T' => {
88						start = i + 1;
89						State::TimeNumber
90					}
91					_ => State::DateNumber,
92				},
93				State::TimeNumber => match c {
94					'H' => {
95						result.hour = Some(&self.0[start..i]);
96						start = i + 1;
97						State::TimeNumber
98					}
99					'M' => {
100						result.minute = Some(&self.0[start..i]);
101						start = i + 1;
102						State::TimeNumber
103					}
104					'S' => {
105						result.second = Some(&self.0[start..i]);
106						start = i + 1;
107						State::TimeNumber
108					}
109					_ => State::TimeNumber,
110				},
111			}
112		}
113
114		result
115	}
116}
117
118impl Lexical for Duration {
119	type Error = InvalidDuration<String>;
120
121	fn parse(value: &str) -> Result<&Self, Self::Error> {
122		Self::new(value).map_err(|_| InvalidDuration(value.to_owned()))
123	}
124}
125
126impl LexicalFormOf<crate::Duration> for Duration {
127	type ValueError = std::convert::Infallible;
128
129	fn try_as_value(&self) -> Result<crate::Duration, Self::ValueError> {
130		Ok(self.parts().to_duration())
131	}
132}
133
134#[derive(Debug, PartialEq, Eq)]
135pub struct Parts<'a> {
136	pub is_negative: bool,
137	pub year: Option<&'a str>,
138	pub month: Option<&'a str>,
139	pub day: Option<&'a str>,
140	pub hour: Option<&'a str>,
141	pub minute: Option<&'a str>,
142	pub second: Option<&'a str>,
143}
144
145impl<'a> Parts<'a> {
146	pub fn new(
147		is_negative: bool,
148		year: Option<&'a str>,
149		month: Option<&'a str>,
150		day: Option<&'a str>,
151		hour: Option<&'a str>,
152		minute: Option<&'a str>,
153		second: Option<&'a str>,
154	) -> Self {
155		Self {
156			is_negative,
157			year,
158			month,
159			day,
160			hour,
161			minute,
162			second,
163		}
164	}
165	fn to_duration(&self) -> crate::Duration {
166		let mut months = 0u32;
167
168		if let Some(y) = self.year {
169			let y: u32 = y.parse().unwrap();
170			months += y * 12;
171		}
172
173		if let Some(m) = self.month {
174			let m: u32 = m.parse().unwrap();
175			months += m;
176		}
177
178		let mut seconds = 0u32;
179
180		if let Some(d) = self.day {
181			let d: u32 = d.parse().unwrap();
182			seconds += d * 24 * 60 * 60;
183		}
184
185		if let Some(h) = self.hour {
186			let h: u32 = h.parse().unwrap();
187			seconds += h * 60 * 60;
188		}
189
190		if let Some(m) = self.minute {
191			let m: u32 = m.parse().unwrap();
192			seconds += m * 60;
193		}
194
195		let mut nano_seconds = 0u32;
196
197		if let Some(s) = self.second {
198			let (s, ns) = parse_seconds_decimal(s);
199			seconds += s;
200			nano_seconds = ns;
201		}
202
203		crate::Duration::new(self.is_negative, months, seconds, nano_seconds)
204	}
205}
206
207#[cfg(test)]
208mod tests {
209	use super::*;
210
211	#[test]
212	fn parsing() {
213		let vectors = [
214			(
215				"P9Y11M",
216				Parts::new(false, Some("9"), Some("11"), None, None, None, None),
217				"P9Y11M",
218			),
219			(
220				"P9Y12M",
221				Parts::new(false, Some("9"), Some("12"), None, None, None, None),
222				"P10Y",
223			),
224			(
225				"-P9Y12M1DT24H01M1.0001S",
226				Parts::new(
227					true,
228					Some("9"),
229					Some("12"),
230					Some("1"),
231					Some("24"),
232					Some("01"),
233					Some("1.0001"),
234				),
235				"-P10Y2DT1M1.0001S",
236			),
237		];
238
239		for (input, parts, normalized) in vectors {
240			let lexical_repr = Duration::new(input).unwrap();
241			assert_eq!(lexical_repr.parts(), parts);
242
243			let value = lexical_repr.try_as_value().unwrap();
244			assert_eq!(value.to_string().as_str(), normalized)
245		}
246	}
247}