/rust/registry/src/index.crates.io-1949cf8c6b5b557f/hifitime-4.2.3/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 | 4.15k | pub fn value_ok(&self, val: i32) -> Result<(), HifitimeError> { |
40 | 4.15k | match &self { |
41 | 1.59k | Self::Year => Ok(()), // No validation |
42 | 0 | Self::YearShort => Ok(()), // No validation |
43 | | Self::Month => { |
44 | 1.34k | if !(0..=13).contains(&val) { |
45 | 196 | Err(HifitimeError::Parse { |
46 | 196 | source: ParsingError::ValueError, |
47 | 196 | details: "invalid month", |
48 | 196 | }) |
49 | | } else { |
50 | 1.14k | Ok(()) |
51 | | } |
52 | | } |
53 | | Self::Day => { |
54 | 1.05k | if !(0..=31).contains(&val) { |
55 | 101 | Err(HifitimeError::Parse { |
56 | 101 | source: ParsingError::ValueError, |
57 | 101 | details: "invalid day", |
58 | 101 | }) |
59 | | } else { |
60 | 958 | Ok(()) |
61 | | } |
62 | | } |
63 | | Self::Hour | Self::OffsetHours => { |
64 | 126 | if !(0..=23).contains(&val) { |
65 | 37 | Err(HifitimeError::Parse { |
66 | 37 | source: ParsingError::ValueError, |
67 | 37 | details: "invalid hour", |
68 | 37 | }) |
69 | | } else { |
70 | 89 | Ok(()) |
71 | | } |
72 | | } |
73 | | Self::Minute | Self::OffsetMinutes => { |
74 | 20 | if !(0..=59).contains(&val) { |
75 | 7 | Err(HifitimeError::Parse { |
76 | 7 | source: ParsingError::ValueError, |
77 | 7 | details: "invalid minutes", |
78 | 7 | }) |
79 | | } else { |
80 | 13 | Ok(()) |
81 | | } |
82 | | } |
83 | | Self::Second => { |
84 | 8 | if !(0..=60).contains(&val) { |
85 | 1 | Err(HifitimeError::Parse { |
86 | 1 | source: ParsingError::ValueError, |
87 | 1 | details: "invalid seconds", |
88 | 1 | }) |
89 | | } else { |
90 | 7 | Ok(()) |
91 | | } |
92 | | } |
93 | | Self::Subsecond => { |
94 | 0 | if val < 0 { |
95 | 0 | Err(HifitimeError::Parse { |
96 | 0 | source: ParsingError::ValueError, |
97 | 0 | details: "invalid subseconds", |
98 | 0 | }) |
99 | | } else { |
100 | 0 | 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 | 4.15k | } |
137 | | |
138 | | /// Returns the position in the array for a Gregorian date for this token |
139 | 5.55k | pub(crate) fn gregorian_position(&self) -> Option<usize> { |
140 | 5.55k | match &self { |
141 | 2.72k | Token::Year | Token::YearShort => Some(0), |
142 | 1.49k | Token::Month => Some(1), |
143 | 1.12k | Token::Day => Some(2), |
144 | 172 | Token::Hour => Some(3), |
145 | 30 | Token::Minute => Some(4), |
146 | 9 | Token::Second => Some(5), |
147 | 1 | Token::Subsecond => Some(6), |
148 | 2 | Token::OffsetHours => Some(7), |
149 | 0 | Token::OffsetMinutes => Some(8), |
150 | 0 | _ => None, |
151 | | } |
152 | 5.55k | } |
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 | 4.34k | pub fn advance_with(&mut self, ending_char: char) -> Result<(), HifitimeError> { |
157 | 4.34k | match &self { |
158 | | Token::Year | Token::YearShort => { |
159 | 2.58k | if ending_char == '-' { |
160 | 1.54k | *self = Token::Month; |
161 | 1.54k | Ok(()) |
162 | | } else { |
163 | 1.03k | Err(HifitimeError::Parse { |
164 | 1.03k | source: ParsingError::UnknownFormat, |
165 | 1.03k | details: "invalid year", |
166 | 1.03k | }) |
167 | | } |
168 | | } |
169 | | Token::Month => { |
170 | 1.33k | if ending_char == '-' { |
171 | 1.19k | *self = Token::Day; |
172 | 1.19k | Ok(()) |
173 | | } else { |
174 | 139 | Err(HifitimeError::Parse { |
175 | 139 | source: ParsingError::UnknownFormat, |
176 | 139 | details: "invalid month", |
177 | 139 | }) |
178 | | } |
179 | | } |
180 | | Token::Day => { |
181 | 311 | if ending_char == 'T' || ending_char == ' ' { |
182 | 255 | *self = Token::Hour; |
183 | 255 | 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 | 86 | if ending_char == ':' { |
193 | 40 | *self = Token::Minute; |
194 | 40 | Ok(()) |
195 | | } else { |
196 | 46 | Err(HifitimeError::Parse { |
197 | 46 | source: ParsingError::UnknownFormat, |
198 | 46 | details: "invalid hour", |
199 | 46 | }) |
200 | | } |
201 | | } |
202 | | Token::Minute => { |
203 | 22 | if ending_char == ':' { |
204 | 12 | *self = Token::Second; |
205 | 12 | Ok(()) |
206 | | } else { |
207 | 10 | Err(HifitimeError::Parse { |
208 | 10 | source: ParsingError::UnknownFormat, |
209 | 10 | details: "invalid minutes", |
210 | 10 | }) |
211 | | } |
212 | | } |
213 | | Token::Second => { |
214 | 8 | if ending_char == '.' { |
215 | 1 | *self = Token::Subsecond; |
216 | 7 | } else if ending_char == ' ' || ending_char == 'Z' { |
217 | 4 | // There are no subseconds here, only room for a time scale |
218 | 4 | *self = Token::Timescale; |
219 | 4 | } else if ending_char == '-' || ending_char == '+' { |
220 | 2 | // There are no subseconds here, but we're seeing the start of an offset |
221 | 2 | *self = Token::OffsetHours; |
222 | 2 | } else { |
223 | 1 | return Err(HifitimeError::Parse { |
224 | 1 | source: ParsingError::UnknownFormat, |
225 | 1 | details: "invalid seconds", |
226 | 1 | }); |
227 | | } |
228 | 7 | Ok(()) |
229 | | } |
230 | | Token::Subsecond => { |
231 | 1 | if ending_char == ' ' || ending_char == 'Z' { |
232 | 0 | // There are no subseconds here, only room for a time scale |
233 | 0 | *self = Token::Timescale; |
234 | 1 | } else if ending_char == '-' || ending_char == '+' { |
235 | 0 | // There are no subseconds here, but we're seeing the start of an offset |
236 | 0 | *self = Token::OffsetHours; |
237 | 0 | } else { |
238 | 1 | return Err(HifitimeError::Parse { |
239 | 1 | source: ParsingError::UnknownFormat, |
240 | 1 | details: "invalid subseconds", |
241 | 1 | }); |
242 | | } |
243 | 0 | Ok(()) |
244 | | } |
245 | | Token::OffsetHours => { |
246 | 1 | if ending_char == ':' { |
247 | 0 | *self = Token::OffsetMinutes; |
248 | 0 | Ok(()) |
249 | | } else { |
250 | 1 | Err(HifitimeError::Parse { |
251 | 1 | source: ParsingError::UnknownFormat, |
252 | 1 | details: "invalid hours offset", |
253 | 1 | }) |
254 | | } |
255 | | } |
256 | | Token::OffsetMinutes => { |
257 | 0 | if ending_char == ' ' || ending_char == 'Z' { |
258 | | // Only room for a time scale |
259 | 0 | *self = Token::Timescale; |
260 | 0 | Ok(()) |
261 | | } else { |
262 | 0 | Err(HifitimeError::Parse { |
263 | 0 | source: ParsingError::UnknownFormat, |
264 | 0 | details: "invalid minutes offset", |
265 | 0 | }) |
266 | | } |
267 | | } |
268 | 0 | _ => Ok(()), |
269 | | } |
270 | 4.34k | } |
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 | | } |