xsd_types/lexical/
time.rs

1use static_regular_grammar::RegularGrammar;
2
3use crate::{utils::byte_index_of, InvalidTimeValue};
4
5use super::{date_time::parse_seconds_decimal, parse_timezone, Lexical, LexicalFormOf};
6
7/// Time.
8///
9/// ```abnf
10/// xsd-time = time [timezone]
11///
12/// time = hour ":" minute ":" second ["." fraction]
13///      / "24:00:00" ["." 1*"0"]
14///
15/// hour = ("0" / "1") DIGIT
16///      / "2" ("0" / "1" / "2" / "3")
17///
18/// minute = ("0" / "1" / "2" / "3" / "4" / "5") DIGIT
19///
20/// second = ("0" / "1" / "2" / "3" / "4" / "5") DIGIT
21///
22/// fraction = 1*DIGIT
23///
24/// timezone = ("+" / "-") ((("0" DIGIT / "1" ("0" / "1" / "2" / "3")) ":" minute) / "14:00")
25///          / %s"Z"
26/// ```
27#[derive(RegularGrammar, PartialEq, Eq, PartialOrd, Ord, Hash)]
28#[grammar(sized(TimeBuf, derive(PartialEq, Eq, PartialOrd, Ord, Hash)))]
29pub struct Time(str);
30
31impl Time {
32	fn parts(&self) -> Parts {
33		let seconds_end =
34			byte_index_of(self.0.as_bytes(), 8, [b'+', b'-', b'Z']).unwrap_or(self.0.len());
35		Parts {
36			hours: &self.0[..2],
37			minutes: &self.0[3..5],
38			seconds: &self.0[6..seconds_end],
39			timezone: if seconds_end == self.0.len() {
40				None
41			} else {
42				Some(&self.0[seconds_end..])
43			},
44		}
45	}
46}
47
48impl Lexical for Time {
49	type Error = InvalidTime<String>;
50
51	fn parse(value: &str) -> Result<&Self, Self::Error> {
52		Self::new(value).map_err(|_| InvalidTime(value.to_owned()))
53	}
54}
55
56impl LexicalFormOf<crate::Time> for Time {
57	type ValueError = InvalidTimeValue;
58
59	fn try_as_value(&self) -> Result<crate::Time, Self::ValueError> {
60		self.parts().to_time()
61	}
62}
63
64#[derive(Debug, PartialEq, Eq)]
65pub struct Parts<'a> {
66	pub hours: &'a str,
67	pub minutes: &'a str,
68	pub seconds: &'a str,
69	pub timezone: Option<&'a str>,
70}
71
72impl<'a> Parts<'a> {
73	pub fn new(
74		hours: &'a str,
75		minutes: &'a str,
76		seconds: &'a str,
77		timezone: Option<&'a str>,
78	) -> Self {
79		Self {
80			hours,
81			minutes,
82			seconds,
83			timezone,
84		}
85	}
86
87	fn to_time(&self) -> Result<crate::Time, crate::InvalidTimeValue> {
88		let (seconds, nanoseconds) = parse_seconds_decimal(self.seconds);
89
90		let time = chrono::NaiveTime::from_hms_nano_opt(
91			self.hours.parse().unwrap(),
92			self.minutes.parse().unwrap(),
93			seconds,
94			nanoseconds,
95		)
96		.ok_or(crate::InvalidTimeValue)?;
97
98		Ok(crate::Time::new(time, self.timezone.map(parse_timezone)))
99	}
100}
101
102#[cfg(test)]
103mod tests {
104	use super::*;
105
106	#[test]
107	fn parsing() {
108		let vectors = [
109			(
110				"13:07:12+01:00",
111				Parts::new("13", "07", "12", Some("+01:00")),
112			),
113			(
114				"12:00:00-05:00",
115				Parts::new("12", "00", "00", Some("-05:00")),
116			),
117			(
118				"12:00:00.00001-05:00",
119				Parts::new("12", "00", "00.00001", Some("-05:00")),
120			),
121		];
122
123		for (input, parts) in vectors {
124			let lexical_repr = Time::new(input).unwrap();
125			assert_eq!(lexical_repr.parts(), parts);
126
127			let value = lexical_repr.try_as_value().unwrap();
128			assert_eq!(value.to_string().as_str(), input)
129		}
130	}
131}