/rust/registry/src/index.crates.io-1949cf8c6b5b557f/hifitime-4.3.1/src/parser.rs
Line | Count | Source |
1 | | /* |
2 | | * Hifitime |
3 | | * Copyright (C) 2017-onward Christopher Rabotin <christopher.rabotin@gmail.com> et al. (cf. https://github.com/nyx-space/hifitime/graphs/contributors) |
4 | | * This Source Code Form is subject to the terms of the Mozilla Public |
5 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
6 | | * file, You can obtain one at https://mozilla.org/MPL/2.0/. |
7 | | * |
8 | | * Documentation: https://nyxspace.com/ |
9 | | */ |
10 | | |
11 | | use crate::{HifitimeError, ParsingError}; |
12 | | |
13 | | #[cfg_attr(kani, derive(kani::Arbitrary))] |
14 | | #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] |
15 | | pub(crate) enum Token { |
16 | | #[default] |
17 | | Year, |
18 | | YearShort, |
19 | | Month, |
20 | | Day, |
21 | | Hour, |
22 | | Minute, |
23 | | Second, |
24 | | Subsecond, |
25 | | OffsetHours, |
26 | | OffsetMinutes, |
27 | | Timescale, |
28 | | DayOfYearInteger, |
29 | | DayOfYear, |
30 | | Weekday, |
31 | | WeekdayShort, |
32 | | WeekdayDecimal, |
33 | | MonthName, |
34 | | MonthNameShort, |
35 | | } |
36 | | |
37 | | impl Token { |
38 | | // Check that the _integer_ value is valid at first sight. |
39 | 24.2k | pub fn value_ok(&self, val: i32) -> Result<(), HifitimeError> { |
40 | 24.2k | match &self { |
41 | 6.75k | Self::Year => Ok(()), // No validation |
42 | 0 | Self::YearShort => Ok(()), // No validation |
43 | | Self::Month => { |
44 | 6.44k | if !(0..=13).contains(&val) { |
45 | 241 | Err(HifitimeError::Parse { |
46 | 241 | source: ParsingError::ValueError, |
47 | 241 | details: "invalid month", |
48 | 241 | }) |
49 | | } else { |
50 | 6.20k | Ok(()) |
51 | | } |
52 | | } |
53 | | Self::Day => { |
54 | 6.12k | if !(0..=31).contains(&val) { |
55 | 201 | Err(HifitimeError::Parse { |
56 | 201 | source: ParsingError::ValueError, |
57 | 201 | details: "invalid day", |
58 | 201 | }) |
59 | | } else { |
60 | 5.92k | Ok(()) |
61 | | } |
62 | | } |
63 | | Self::Hour | Self::OffsetHours => { |
64 | 2.17k | if !(0..=23).contains(&val) { |
65 | 225 | Err(HifitimeError::Parse { |
66 | 225 | source: ParsingError::ValueError, |
67 | 225 | details: "invalid hour", |
68 | 225 | }) |
69 | | } else { |
70 | 1.95k | Ok(()) |
71 | | } |
72 | | } |
73 | | Self::Minute | Self::OffsetMinutes => { |
74 | 1.29k | if !(0..=59).contains(&val) { |
75 | 170 | Err(HifitimeError::Parse { |
76 | 170 | source: ParsingError::ValueError, |
77 | 170 | details: "invalid minutes", |
78 | 170 | }) |
79 | | } else { |
80 | 1.12k | Ok(()) |
81 | | } |
82 | | } |
83 | | Self::Second => { |
84 | 955 | if !(0..=60).contains(&val) { |
85 | 142 | Err(HifitimeError::Parse { |
86 | 142 | source: ParsingError::ValueError, |
87 | 142 | details: "invalid seconds", |
88 | 142 | }) |
89 | | } else { |
90 | 813 | Ok(()) |
91 | | } |
92 | | } |
93 | | Self::Subsecond => { |
94 | 478 | if val < 0 { |
95 | 0 | Err(HifitimeError::Parse { |
96 | 0 | source: ParsingError::ValueError, |
97 | 0 | details: "invalid subseconds", |
98 | 0 | }) |
99 | | } else { |
100 | 478 | Ok(()) |
101 | | } |
102 | | } |
103 | 0 | Self::Timescale => Ok(()), |
104 | | Self::DayOfYearInteger => { |
105 | 0 | if !(0..=366).contains(&val) { |
106 | 0 | Err(HifitimeError::Parse { |
107 | 0 | source: ParsingError::ValueError, |
108 | 0 | details: "invalid day of year", |
109 | 0 | }) |
110 | | } else { |
111 | 0 | Ok(()) |
112 | | } |
113 | | } |
114 | | Self::WeekdayDecimal => { |
115 | 0 | if !(0..=6).contains(&val) { |
116 | 0 | Err(HifitimeError::Parse { |
117 | 0 | source: ParsingError::ValueError, |
118 | 0 | details: "invalid weekday decimal (must be 0-6)", |
119 | 0 | }) |
120 | | } else { |
121 | 0 | Ok(()) |
122 | | } |
123 | | } |
124 | | Self::Weekday |
125 | | | Self::WeekdayShort |
126 | | | Self::MonthName |
127 | | | Self::MonthNameShort |
128 | | | Self::DayOfYear => { |
129 | | // These cannot be parsed as integers |
130 | 0 | Err(HifitimeError::Parse { |
131 | 0 | source: ParsingError::ValueError, |
132 | 0 | details: "invalid name or day of year", |
133 | 0 | }) |
134 | | } |
135 | | } |
136 | 24.2k | } |
137 | | |
138 | | /// Returns the position in the array for a Gregorian date for this token |
139 | 25.8k | pub(crate) fn gregorian_position(&self) -> Option<usize> { |
140 | 25.8k | match &self { |
141 | 7.83k | Token::Year | Token::YearShort => Some(0), |
142 | 6.64k | Token::Month => Some(1), |
143 | 6.18k | Token::Day => Some(2), |
144 | 1.98k | Token::Hour => Some(3), |
145 | 1.25k | Token::Minute => Some(4), |
146 | 970 | Token::Second => Some(5), |
147 | 501 | Token::Subsecond => Some(6), |
148 | 343 | Token::OffsetHours => Some(7), |
149 | 132 | Token::OffsetMinutes => Some(8), |
150 | 0 | _ => None, |
151 | | } |
152 | 25.8k | } |
153 | | |
154 | | /// Updates the token to what it should be seeking next given the delimiting character |
155 | | /// and returns the position in the array where the parsed integer should live |
156 | 20.6k | pub fn advance_with(&mut self, ending_char: char) -> Result<(), HifitimeError> { |
157 | 20.6k | match &self { |
158 | | Token::Year | Token::YearShort => { |
159 | 7.64k | if ending_char == '-' { |
160 | 6.69k | *self = Token::Month; |
161 | 6.69k | Ok(()) |
162 | | } else { |
163 | 948 | Err(HifitimeError::Parse { |
164 | 948 | source: ParsingError::UnknownFormat, |
165 | 948 | details: "invalid year", |
166 | 948 | }) |
167 | | } |
168 | | } |
169 | | Token::Month => { |
170 | 6.44k | if ending_char == '-' { |
171 | 6.27k | *self = Token::Day; |
172 | 6.27k | Ok(()) |
173 | | } else { |
174 | 168 | Err(HifitimeError::Parse { |
175 | 168 | source: ParsingError::UnknownFormat, |
176 | 168 | details: "invalid month", |
177 | 168 | }) |
178 | | } |
179 | | } |
180 | | Token::Day => { |
181 | 2.61k | if ending_char == 'T' || ending_char == ' ' { |
182 | 2.55k | *self = Token::Hour; |
183 | 2.55k | Ok(()) |
184 | | } else { |
185 | 56 | Err(HifitimeError::Parse { |
186 | 56 | source: ParsingError::UnknownFormat, |
187 | 56 | details: "invalid day", |
188 | 56 | }) |
189 | | } |
190 | | } |
191 | | Token::Hour => { |
192 | 1.42k | if ending_char == ':' { |
193 | 1.34k | *self = Token::Minute; |
194 | 1.34k | Ok(()) |
195 | | } else { |
196 | 80 | Err(HifitimeError::Parse { |
197 | 80 | source: ParsingError::UnknownFormat, |
198 | 80 | details: "invalid hour", |
199 | 80 | }) |
200 | | } |
201 | | } |
202 | | Token::Minute => { |
203 | 1.14k | if ending_char == ':' { |
204 | 1.05k | *self = Token::Second; |
205 | 1.05k | Ok(()) |
206 | | } else { |
207 | 83 | Err(HifitimeError::Parse { |
208 | 83 | source: ParsingError::UnknownFormat, |
209 | 83 | details: "invalid minutes", |
210 | 83 | }) |
211 | | } |
212 | | } |
213 | | Token::Second => { |
214 | 863 | if ending_char == '.' { |
215 | 518 | *self = Token::Subsecond; |
216 | 518 | } else if ending_char == ' ' || ending_char == 'Z' { |
217 | 154 | // There are no subseconds here, only room for a time scale |
218 | 154 | *self = Token::Timescale; |
219 | 191 | } else if ending_char == '-' || ending_char == '+' { |
220 | 181 | // There are no subseconds here, but we're seeing the start of an offset |
221 | 181 | *self = Token::OffsetHours; |
222 | 181 | } else { |
223 | 10 | return Err(HifitimeError::Parse { |
224 | 10 | source: ParsingError::UnknownFormat, |
225 | 10 | details: "invalid seconds", |
226 | 10 | }); |
227 | | } |
228 | 853 | Ok(()) |
229 | | } |
230 | | Token::Subsecond => { |
231 | 339 | if ending_char == ' ' || ending_char == 'Z' { |
232 | 96 | // There are no subseconds here, only room for a time scale |
233 | 96 | *self = Token::Timescale; |
234 | 243 | } else if ending_char == '-' || ending_char == '+' { |
235 | 233 | // There are no subseconds here, but we're seeing the start of an offset |
236 | 233 | *self = Token::OffsetHours; |
237 | 233 | } else { |
238 | 10 | return Err(HifitimeError::Parse { |
239 | 10 | source: ParsingError::UnknownFormat, |
240 | 10 | details: "invalid subseconds", |
241 | 10 | }); |
242 | | } |
243 | 329 | Ok(()) |
244 | | } |
245 | | Token::OffsetHours => { |
246 | 195 | if ending_char == ':' { |
247 | 147 | *self = Token::OffsetMinutes; |
248 | 147 | Ok(()) |
249 | | } else { |
250 | 48 | Err(HifitimeError::Parse { |
251 | 48 | source: ParsingError::UnknownFormat, |
252 | 48 | details: "invalid hours offset", |
253 | 48 | }) |
254 | | } |
255 | | } |
256 | | Token::OffsetMinutes => { |
257 | 28 | if ending_char == ' ' || ending_char == 'Z' { |
258 | | // Only room for a time scale |
259 | 20 | *self = Token::Timescale; |
260 | 20 | Ok(()) |
261 | | } else { |
262 | 8 | Err(HifitimeError::Parse { |
263 | 8 | source: ParsingError::UnknownFormat, |
264 | 8 | details: "invalid minutes offset", |
265 | 8 | }) |
266 | | } |
267 | | } |
268 | 0 | _ => Ok(()), |
269 | | } |
270 | 20.6k | } |
271 | | |
272 | 0 | pub(crate) const fn is_numeric(self) -> bool { |
273 | 0 | !matches!( |
274 | 0 | self, |
275 | | Token::Timescale |
276 | | | Token::Weekday |
277 | | | Token::WeekdayShort |
278 | | | Token::MonthName |
279 | | | Token::MonthNameShort |
280 | | ) |
281 | 0 | } |
282 | | |
283 | 0 | pub(crate) const fn fixed_length(self) -> Option<usize> { |
284 | 0 | match self { |
285 | 0 | Token::Year => Some(4), |
286 | | Token::YearShort |
287 | | | Token::Month |
288 | | | Token::Day |
289 | | | Token::Hour |
290 | | | Token::Minute |
291 | | | Token::Second |
292 | | | Token::OffsetHours |
293 | 0 | | Token::OffsetMinutes => Some(2), |
294 | 0 | Token::DayOfYearInteger => Some(3), |
295 | 0 | Token::WeekdayDecimal => Some(1), |
296 | 0 | _ => None, |
297 | | } |
298 | 0 | } |
299 | | } |