/rust/registry/src/index.crates.io-6f17d22bba15001f/time-0.3.13/src/parsing/parsable.rs
Line | Count | Source (jump to first uncovered line) |
1 | | //! A trait that can be used to parse an item from an input. |
2 | | |
3 | | use core::ops::Deref; |
4 | | |
5 | | use crate::error::TryFromParsed; |
6 | | use crate::format_description::well_known::iso8601::EncodedConfig; |
7 | | use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339}; |
8 | | use crate::format_description::FormatItem; |
9 | | use crate::parsing::{Parsed, ParsedItem}; |
10 | | use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; |
11 | | |
12 | | /// A type that can be parsed. |
13 | | #[cfg_attr(__time_03_docs, doc(notable_trait))] |
14 | | pub trait Parsable: sealed::Sealed {} |
15 | | impl Parsable for FormatItem<'_> {} |
16 | | impl Parsable for [FormatItem<'_>] {} |
17 | | impl Parsable for Rfc2822 {} |
18 | | impl Parsable for Rfc3339 {} |
19 | | impl<const CONFIG: EncodedConfig> Parsable for Iso8601<CONFIG> {} |
20 | | impl<T: Deref> Parsable for T where T::Target: Parsable {} |
21 | | |
22 | | /// Seal the trait to prevent downstream users from implementing it, while still allowing it to |
23 | | /// exist in generic bounds. |
24 | | mod sealed { |
25 | | |
26 | | #[allow(clippy::wildcard_imports)] |
27 | | use super::*; |
28 | | |
29 | | /// Parse the item using a format description and an input. |
30 | | #[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))] |
31 | | pub trait Sealed { |
32 | | /// Parse the item into the provided [`Parsed`] struct. |
33 | | /// |
34 | | /// This method can be used to parse a single component without parsing the full value. |
35 | | fn parse_into<'a>( |
36 | | &self, |
37 | | input: &'a [u8], |
38 | | parsed: &mut Parsed, |
39 | | ) -> Result<&'a [u8], error::Parse>; |
40 | | |
41 | | /// Parse the item into a new [`Parsed`] struct. |
42 | | /// |
43 | | /// This method can only be used to parse a complete value of a type. If any characters |
44 | | /// remain after parsing, an error will be returned. |
45 | 0 | fn parse(&self, input: &[u8]) -> Result<Parsed, error::Parse> { |
46 | 0 | let mut parsed = Parsed::new(); |
47 | 0 | if self.parse_into(input, &mut parsed)?.is_empty() { |
48 | 0 | Ok(parsed) |
49 | | } else { |
50 | 0 | Err(error::Parse::UnexpectedTrailingCharacters) |
51 | | } |
52 | 0 | } |
53 | | |
54 | | /// Parse a [`Date`] from the format description. |
55 | 0 | fn parse_date(&self, input: &[u8]) -> Result<Date, error::Parse> { |
56 | 0 | Ok(self.parse(input)?.try_into()?) |
57 | 0 | } |
58 | | |
59 | | /// Parse a [`Time`] from the format description. |
60 | 0 | fn parse_time(&self, input: &[u8]) -> Result<Time, error::Parse> { |
61 | 0 | Ok(self.parse(input)?.try_into()?) |
62 | 0 | } |
63 | | |
64 | | /// Parse a [`UtcOffset`] from the format description. |
65 | 0 | fn parse_offset(&self, input: &[u8]) -> Result<UtcOffset, error::Parse> { |
66 | 0 | Ok(self.parse(input)?.try_into()?) |
67 | 0 | } |
68 | | |
69 | | /// Parse a [`PrimitiveDateTime`] from the format description. |
70 | 0 | fn parse_date_time(&self, input: &[u8]) -> Result<PrimitiveDateTime, error::Parse> { |
71 | 0 | Ok(self.parse(input)?.try_into()?) |
72 | 0 | } |
73 | | |
74 | | /// Parse a [`OffsetDateTime`] from the format description. |
75 | 0 | fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> { |
76 | 0 | Ok(self.parse(input)?.try_into()?) |
77 | 0 | } |
78 | | } |
79 | | } |
80 | | |
81 | | // region: custom formats |
82 | | impl sealed::Sealed for FormatItem<'_> { |
83 | 0 | fn parse_into<'a>( |
84 | 0 | &self, |
85 | 0 | input: &'a [u8], |
86 | 0 | parsed: &mut Parsed, |
87 | 0 | ) -> Result<&'a [u8], error::Parse> { |
88 | 0 | Ok(parsed.parse_item(input, self)?) |
89 | 0 | } |
90 | | } |
91 | | |
92 | | impl sealed::Sealed for [FormatItem<'_>] { |
93 | 0 | fn parse_into<'a>( |
94 | 0 | &self, |
95 | 0 | input: &'a [u8], |
96 | 0 | parsed: &mut Parsed, |
97 | 0 | ) -> Result<&'a [u8], error::Parse> { |
98 | 0 | Ok(parsed.parse_items(input, self)?) |
99 | 0 | } |
100 | | } |
101 | | |
102 | | impl<T: Deref> sealed::Sealed for T |
103 | | where |
104 | | T::Target: sealed::Sealed, |
105 | | { |
106 | 0 | fn parse_into<'a>( |
107 | 0 | &self, |
108 | 0 | input: &'a [u8], |
109 | 0 | parsed: &mut Parsed, |
110 | 0 | ) -> Result<&'a [u8], error::Parse> { |
111 | 0 | self.deref().parse_into(input, parsed) |
112 | 0 | } |
113 | | } |
114 | | // endregion custom formats |
115 | | |
116 | | // region: well-known formats |
117 | | impl sealed::Sealed for Rfc2822 { |
118 | 0 | fn parse_into<'a>( |
119 | 0 | &self, |
120 | 0 | input: &'a [u8], |
121 | 0 | parsed: &mut Parsed, |
122 | 0 | ) -> Result<&'a [u8], error::Parse> { |
123 | | use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; |
124 | | use crate::parsing::combinator::rfc::rfc2822::{cfws, fws}; |
125 | | use crate::parsing::combinator::{ |
126 | | ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign, |
127 | | }; |
128 | | |
129 | 0 | let colon = ascii_char::<b':'>; |
130 | 0 | let comma = ascii_char::<b','>; |
131 | 0 |
|
132 | 0 | let input = opt(fws)(input).into_inner(); |
133 | 0 | let input = first_match( |
134 | 0 | [ |
135 | 0 | (b"Mon".as_slice(), Weekday::Monday), |
136 | 0 | (b"Tue".as_slice(), Weekday::Tuesday), |
137 | 0 | (b"Wed".as_slice(), Weekday::Wednesday), |
138 | 0 | (b"Thu".as_slice(), Weekday::Thursday), |
139 | 0 | (b"Fri".as_slice(), Weekday::Friday), |
140 | 0 | (b"Sat".as_slice(), Weekday::Saturday), |
141 | 0 | (b"Sun".as_slice(), Weekday::Sunday), |
142 | 0 | ], |
143 | 0 | false, |
144 | 0 | )(input) |
145 | 0 | .and_then(|item| item.consume_value(|value| parsed.set_weekday(value))) |
146 | 0 | .ok_or(InvalidComponent("weekday"))?; |
147 | 0 | let input = comma(input).ok_or(InvalidLiteral)?.into_inner(); |
148 | 0 | let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); |
149 | 0 | let input = n_to_m_digits::<_, 1, 2>(input) |
150 | 0 | .and_then(|item| item.consume_value(|value| parsed.set_day(value))) |
151 | 0 | .ok_or(InvalidComponent("day"))?; |
152 | 0 | let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); |
153 | 0 | let input = first_match( |
154 | 0 | [ |
155 | 0 | (b"Jan".as_slice(), Month::January), |
156 | 0 | (b"Feb".as_slice(), Month::February), |
157 | 0 | (b"Mar".as_slice(), Month::March), |
158 | 0 | (b"Apr".as_slice(), Month::April), |
159 | 0 | (b"May".as_slice(), Month::May), |
160 | 0 | (b"Jun".as_slice(), Month::June), |
161 | 0 | (b"Jul".as_slice(), Month::July), |
162 | 0 | (b"Aug".as_slice(), Month::August), |
163 | 0 | (b"Sep".as_slice(), Month::September), |
164 | 0 | (b"Oct".as_slice(), Month::October), |
165 | 0 | (b"Nov".as_slice(), Month::November), |
166 | 0 | (b"Dec".as_slice(), Month::December), |
167 | 0 | ], |
168 | 0 | false, |
169 | 0 | )(input) |
170 | 0 | .and_then(|item| item.consume_value(|value| parsed.set_month(value))) |
171 | 0 | .ok_or(InvalidComponent("month"))?; |
172 | 0 | let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); |
173 | 0 | let input = match exactly_n_digits::<u32, 4>(input) { |
174 | 0 | Some(item) => { |
175 | 0 | let input = item |
176 | 0 | .flat_map(|year| if year >= 1900 { Some(year) } else { None }) |
177 | 0 | .and_then(|item| item.consume_value(|value| parsed.set_year(value as _))) |
178 | 0 | .ok_or(InvalidComponent("year"))?; |
179 | 0 | fws(input).ok_or(InvalidLiteral)?.into_inner() |
180 | | } |
181 | | None => { |
182 | 0 | let input = exactly_n_digits::<u32, 2>(input) |
183 | 0 | .and_then(|item| { |
184 | 0 | item.map(|year| if year < 50 { year + 2000 } else { year + 1900 }) |
185 | 0 | .map(|year| year as _) |
186 | 0 | .consume_value(|value| parsed.set_year(value)) |
187 | 0 | }) |
188 | 0 | .ok_or(InvalidComponent("year"))?; |
189 | 0 | cfws(input).ok_or(InvalidLiteral)?.into_inner() |
190 | | } |
191 | | }; |
192 | | |
193 | 0 | let input = exactly_n_digits::<_, 2>(input) |
194 | 0 | .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value))) |
195 | 0 | .ok_or(InvalidComponent("hour"))?; |
196 | 0 | let input = opt(cfws)(input).into_inner(); |
197 | 0 | let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); |
198 | 0 | let input = opt(cfws)(input).into_inner(); |
199 | 0 | let input = exactly_n_digits::<_, 2>(input) |
200 | 0 | .and_then(|item| item.consume_value(|value| parsed.set_minute(value))) |
201 | 0 | .ok_or(InvalidComponent("minute"))?; |
202 | | |
203 | 0 | let input = if let Some(input) = colon(opt(cfws)(input).into_inner()) { |
204 | 0 | let input = input.into_inner(); // discard the colon |
205 | 0 | let input = opt(cfws)(input).into_inner(); |
206 | 0 | let input = exactly_n_digits::<_, 2>(input) |
207 | 0 | .and_then(|item| item.consume_value(|value| parsed.set_second(value))) |
208 | 0 | .ok_or(InvalidComponent("second"))?; |
209 | 0 | cfws(input).ok_or(InvalidLiteral)?.into_inner() |
210 | | } else { |
211 | 0 | cfws(input).ok_or(InvalidLiteral)?.into_inner() |
212 | | }; |
213 | | |
214 | | // The RFC explicitly allows leap seconds. |
215 | 0 | parsed.set_leap_second_allowed(true); |
216 | 0 |
|
217 | 0 | #[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522 |
218 | 0 | let zone_literal = first_match( |
219 | 0 | [ |
220 | 0 | (b"UT".as_slice(), 0), |
221 | 0 | (b"GMT".as_slice(), 0), |
222 | 0 | (b"EST".as_slice(), -5), |
223 | 0 | (b"EDT".as_slice(), -4), |
224 | 0 | (b"CST".as_slice(), -6), |
225 | 0 | (b"CDT".as_slice(), -5), |
226 | 0 | (b"MST".as_slice(), -7), |
227 | 0 | (b"MDT".as_slice(), -6), |
228 | 0 | (b"PST".as_slice(), -8), |
229 | 0 | (b"PDT".as_slice(), -7), |
230 | 0 | ], |
231 | 0 | false, |
232 | 0 | )(input) |
233 | 0 | .or_else(|| match input { |
234 | | [ |
235 | 0 | b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z', |
236 | 0 | rest @ .., |
237 | 0 | ] => Some(ParsedItem(rest, 0)), |
238 | 0 | _ => None, |
239 | 0 | }); |
240 | 0 | if let Some(zone_literal) = zone_literal { |
241 | 0 | let input = zone_literal |
242 | 0 | .consume_value(|value| parsed.set_offset_hour(value)) |
243 | 0 | .ok_or(InvalidComponent("offset hour"))?; |
244 | 0 | parsed |
245 | 0 | .set_offset_minute_signed(0) |
246 | 0 | .ok_or(InvalidComponent("offset minute"))?; |
247 | 0 | parsed |
248 | 0 | .set_offset_second_signed(0) |
249 | 0 | .ok_or(InvalidComponent("offset second"))?; |
250 | 0 | return Ok(input); |
251 | 0 | } |
252 | | |
253 | 0 | let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?; |
254 | 0 | let input = exactly_n_digits::<u8, 2>(input) |
255 | 0 | .and_then(|item| { |
256 | 0 | item.map(|offset_hour| { |
257 | 0 | if offset_sign == b'-' { |
258 | 0 | -(offset_hour as i8) |
259 | | } else { |
260 | 0 | offset_hour as _ |
261 | | } |
262 | 0 | }) |
263 | 0 | .consume_value(|value| parsed.set_offset_hour(value)) |
264 | 0 | }) |
265 | 0 | .ok_or(InvalidComponent("offset hour"))?; |
266 | 0 | let input = exactly_n_digits::<u8, 2>(input) |
267 | 0 | .and_then(|item| { |
268 | 0 | item.consume_value(|value| parsed.set_offset_minute_signed(value as _)) |
269 | 0 | }) |
270 | 0 | .ok_or(InvalidComponent("offset minute"))?; |
271 | | |
272 | 0 | Ok(input) |
273 | 0 | } |
274 | | |
275 | 0 | fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> { |
276 | | use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; |
277 | | use crate::parsing::combinator::rfc::rfc2822::{cfws, fws}; |
278 | | use crate::parsing::combinator::{ |
279 | | ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign, |
280 | | }; |
281 | | |
282 | 0 | let colon = ascii_char::<b':'>; |
283 | 0 | let comma = ascii_char::<b','>; |
284 | 0 |
|
285 | 0 | let input = opt(fws)(input).into_inner(); |
286 | | // This parses the weekday, but we don't actually use the value anywhere. Because of this, |
287 | | // just return `()` to avoid unnecessary generated code. |
288 | 0 | let ParsedItem(input, ()) = first_match( |
289 | 0 | [ |
290 | 0 | (b"Mon".as_slice(), ()), |
291 | 0 | (b"Tue".as_slice(), ()), |
292 | 0 | (b"Wed".as_slice(), ()), |
293 | 0 | (b"Thu".as_slice(), ()), |
294 | 0 | (b"Fri".as_slice(), ()), |
295 | 0 | (b"Sat".as_slice(), ()), |
296 | 0 | (b"Sun".as_slice(), ()), |
297 | 0 | ], |
298 | 0 | false, |
299 | 0 | )(input) |
300 | 0 | .ok_or(InvalidComponent("weekday"))?; |
301 | 0 | let input = comma(input).ok_or(InvalidLiteral)?.into_inner(); |
302 | 0 | let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); |
303 | 0 | let ParsedItem(input, day) = |
304 | 0 | n_to_m_digits::<_, 1, 2>(input).ok_or(InvalidComponent("day"))?; |
305 | 0 | let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); |
306 | 0 | let ParsedItem(input, month) = first_match( |
307 | 0 | [ |
308 | 0 | (b"Jan".as_slice(), Month::January), |
309 | 0 | (b"Feb".as_slice(), Month::February), |
310 | 0 | (b"Mar".as_slice(), Month::March), |
311 | 0 | (b"Apr".as_slice(), Month::April), |
312 | 0 | (b"May".as_slice(), Month::May), |
313 | 0 | (b"Jun".as_slice(), Month::June), |
314 | 0 | (b"Jul".as_slice(), Month::July), |
315 | 0 | (b"Aug".as_slice(), Month::August), |
316 | 0 | (b"Sep".as_slice(), Month::September), |
317 | 0 | (b"Oct".as_slice(), Month::October), |
318 | 0 | (b"Nov".as_slice(), Month::November), |
319 | 0 | (b"Dec".as_slice(), Month::December), |
320 | 0 | ], |
321 | 0 | false, |
322 | 0 | )(input) |
323 | 0 | .ok_or(InvalidComponent("month"))?; |
324 | 0 | let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); |
325 | 0 | let (input, year) = match exactly_n_digits::<u32, 4>(input) { |
326 | 0 | Some(item) => { |
327 | 0 | let ParsedItem(input, year) = item |
328 | 0 | .flat_map(|year| if year >= 1900 { Some(year) } else { None }) |
329 | 0 | .ok_or(InvalidComponent("year"))?; |
330 | 0 | let input = fws(input).ok_or(InvalidLiteral)?.into_inner(); |
331 | 0 | (input, year) |
332 | | } |
333 | | None => { |
334 | 0 | let ParsedItem(input, year) = exactly_n_digits::<u32, 2>(input) |
335 | 0 | .map(|item| item.map(|year| if year < 50 { year + 2000 } else { year + 1900 })) |
336 | 0 | .ok_or(InvalidComponent("year"))?; |
337 | 0 | let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); |
338 | 0 | (input, year) |
339 | | } |
340 | | }; |
341 | | |
342 | 0 | let ParsedItem(input, hour) = |
343 | 0 | exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("hour"))?; |
344 | 0 | let input = opt(cfws)(input).into_inner(); |
345 | 0 | let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); |
346 | 0 | let input = opt(cfws)(input).into_inner(); |
347 | 0 | let ParsedItem(input, minute) = |
348 | 0 | exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("minute"))?; |
349 | | |
350 | 0 | let (input, mut second) = if let Some(input) = colon(opt(cfws)(input).into_inner()) { |
351 | 0 | let input = input.into_inner(); // discard the colon |
352 | 0 | let input = opt(cfws)(input).into_inner(); |
353 | 0 | let ParsedItem(input, second) = |
354 | 0 | exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("second"))?; |
355 | 0 | let input = cfws(input).ok_or(InvalidLiteral)?.into_inner(); |
356 | 0 | (input, second) |
357 | | } else { |
358 | 0 | (cfws(input).ok_or(InvalidLiteral)?.into_inner(), 0) |
359 | | }; |
360 | | |
361 | | #[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522 |
362 | 0 | let zone_literal = first_match( |
363 | 0 | [ |
364 | 0 | (b"UT".as_slice(), 0), |
365 | 0 | (b"GMT".as_slice(), 0), |
366 | 0 | (b"EST".as_slice(), -5), |
367 | 0 | (b"EDT".as_slice(), -4), |
368 | 0 | (b"CST".as_slice(), -6), |
369 | 0 | (b"CDT".as_slice(), -5), |
370 | 0 | (b"MST".as_slice(), -7), |
371 | 0 | (b"MDT".as_slice(), -6), |
372 | 0 | (b"PST".as_slice(), -8), |
373 | 0 | (b"PDT".as_slice(), -7), |
374 | 0 | ], |
375 | 0 | false, |
376 | 0 | )(input) |
377 | 0 | .or_else(|| match input { |
378 | | [ |
379 | 0 | b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z', |
380 | 0 | rest @ .., |
381 | 0 | ] => Some(ParsedItem(rest, 0)), |
382 | 0 | _ => None, |
383 | 0 | }); |
384 | | |
385 | 0 | let (input, offset_hour, offset_minute) = if let Some(zone_literal) = zone_literal { |
386 | 0 | let ParsedItem(input, offset_hour) = zone_literal; |
387 | 0 | (input, offset_hour, 0) |
388 | | } else { |
389 | 0 | let ParsedItem(input, offset_sign) = |
390 | 0 | sign(input).ok_or(InvalidComponent("offset hour"))?; |
391 | 0 | let ParsedItem(input, offset_hour) = exactly_n_digits::<u8, 2>(input) |
392 | 0 | .map(|item| { |
393 | 0 | item.map(|offset_hour| { |
394 | 0 | if offset_sign == b'-' { |
395 | 0 | -(offset_hour as i8) |
396 | | } else { |
397 | 0 | offset_hour as _ |
398 | | } |
399 | 0 | }) |
400 | 0 | }) |
401 | 0 | .ok_or(InvalidComponent("offset hour"))?; |
402 | 0 | let ParsedItem(input, offset_minute) = |
403 | 0 | exactly_n_digits::<u8, 2>(input).ok_or(InvalidComponent("offset minute"))?; |
404 | 0 | (input, offset_hour, offset_minute as i8) |
405 | | }; |
406 | | |
407 | 0 | if !input.is_empty() { |
408 | 0 | return Err(error::Parse::UnexpectedTrailingCharacters); |
409 | 0 | } |
410 | 0 |
|
411 | 0 | let mut nanosecond = 0; |
412 | 0 | let leap_second_input = if second == 60 { |
413 | 0 | second = 59; |
414 | 0 | nanosecond = 999_999_999; |
415 | 0 | true |
416 | | } else { |
417 | 0 | false |
418 | | }; |
419 | | |
420 | 0 | let dt = (|| { |
421 | 0 | let date = Date::from_calendar_date(year as _, month, day)?; |
422 | 0 | let time = Time::from_hms_nano(hour, minute, second, nanosecond)?; |
423 | 0 | let offset = UtcOffset::from_hms(offset_hour, offset_minute, 0)?; |
424 | 0 | Ok(date.with_time(time).assume_offset(offset)) |
425 | 0 | })() |
426 | 0 | .map_err(TryFromParsed::ComponentRange)?; |
427 | | |
428 | 0 | if leap_second_input && !dt.is_valid_leap_second_stand_in() { |
429 | 0 | return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange( |
430 | 0 | error::ComponentRange { |
431 | 0 | name: "second", |
432 | 0 | minimum: 0, |
433 | 0 | maximum: 59, |
434 | 0 | value: 60, |
435 | 0 | conditional_range: true, |
436 | 0 | }, |
437 | 0 | ))); |
438 | 0 | } |
439 | 0 |
|
440 | 0 | Ok(dt) |
441 | 0 | } |
442 | | } |
443 | | |
444 | | impl sealed::Sealed for Rfc3339 { |
445 | 0 | fn parse_into<'a>( |
446 | 0 | &self, |
447 | 0 | input: &'a [u8], |
448 | 0 | parsed: &mut Parsed, |
449 | 0 | ) -> Result<&'a [u8], error::Parse> { |
450 | | use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; |
451 | | use crate::parsing::combinator::{ |
452 | | any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign, |
453 | | }; |
454 | | |
455 | 0 | let dash = ascii_char::<b'-'>; |
456 | 0 | let colon = ascii_char::<b':'>; |
457 | | |
458 | 0 | let input = exactly_n_digits::<u32, 4>(input) |
459 | 0 | .and_then(|item| item.consume_value(|value| parsed.set_year(value as _))) |
460 | 0 | .ok_or(InvalidComponent("year"))?; |
461 | 0 | let input = dash(input).ok_or(InvalidLiteral)?.into_inner(); |
462 | 0 | let input = exactly_n_digits::<_, 2>(input) |
463 | 0 | .and_then(|item| item.flat_map(|value| Month::from_number(value).ok())) |
464 | 0 | .and_then(|item| item.consume_value(|value| parsed.set_month(value))) |
465 | 0 | .ok_or(InvalidComponent("month"))?; |
466 | 0 | let input = dash(input).ok_or(InvalidLiteral)?.into_inner(); |
467 | 0 | let input = exactly_n_digits::<_, 2>(input) |
468 | 0 | .and_then(|item| item.consume_value(|value| parsed.set_day(value))) |
469 | 0 | .ok_or(InvalidComponent("day"))?; |
470 | 0 | let input = ascii_char_ignore_case::<b'T'>(input) |
471 | 0 | .ok_or(InvalidLiteral)? |
472 | 0 | .into_inner(); |
473 | 0 | let input = exactly_n_digits::<_, 2>(input) |
474 | 0 | .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value))) |
475 | 0 | .ok_or(InvalidComponent("hour"))?; |
476 | 0 | let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); |
477 | 0 | let input = exactly_n_digits::<_, 2>(input) |
478 | 0 | .and_then(|item| item.consume_value(|value| parsed.set_minute(value))) |
479 | 0 | .ok_or(InvalidComponent("minute"))?; |
480 | 0 | let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); |
481 | 0 | let input = exactly_n_digits::<_, 2>(input) |
482 | 0 | .and_then(|item| item.consume_value(|value| parsed.set_second(value))) |
483 | 0 | .ok_or(InvalidComponent("second"))?; |
484 | 0 | let input = if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) { |
485 | 0 | let ParsedItem(mut input, mut value) = any_digit(input) |
486 | 0 | .ok_or(InvalidComponent("subsecond"))? |
487 | 0 | .map(|v| (v - b'0') as u32 * 100_000_000); |
488 | 0 |
|
489 | 0 | let mut multiplier = 10_000_000; |
490 | 0 | while let Some(ParsedItem(new_input, digit)) = any_digit(input) { |
491 | 0 | value += (digit - b'0') as u32 * multiplier; |
492 | 0 | input = new_input; |
493 | 0 | multiplier /= 10; |
494 | 0 | } |
495 | | |
496 | 0 | parsed |
497 | 0 | .set_subsecond(value) |
498 | 0 | .ok_or(InvalidComponent("subsecond"))?; |
499 | 0 | input |
500 | | } else { |
501 | 0 | input |
502 | | }; |
503 | | |
504 | | // The RFC explicitly allows leap seconds. |
505 | 0 | parsed.set_leap_second_allowed(true); |
506 | | |
507 | 0 | if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) { |
508 | 0 | parsed |
509 | 0 | .set_offset_hour(0) |
510 | 0 | .ok_or(InvalidComponent("offset hour"))?; |
511 | 0 | parsed |
512 | 0 | .set_offset_minute_signed(0) |
513 | 0 | .ok_or(InvalidComponent("offset minute"))?; |
514 | 0 | parsed |
515 | 0 | .set_offset_second_signed(0) |
516 | 0 | .ok_or(InvalidComponent("offset second"))?; |
517 | 0 | return Ok(input); |
518 | 0 | } |
519 | | |
520 | 0 | let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?; |
521 | 0 | let input = exactly_n_digits::<u8, 2>(input) |
522 | 0 | .and_then(|item| { |
523 | 0 | item.map(|offset_hour| { |
524 | 0 | if offset_sign == b'-' { |
525 | 0 | -(offset_hour as i8) |
526 | | } else { |
527 | 0 | offset_hour as _ |
528 | | } |
529 | 0 | }) |
530 | 0 | .consume_value(|value| parsed.set_offset_hour(value)) |
531 | 0 | }) |
532 | 0 | .ok_or(InvalidComponent("offset hour"))?; |
533 | 0 | let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); |
534 | 0 | let input = exactly_n_digits::<u8, 2>(input) |
535 | 0 | .and_then(|item| { |
536 | 0 | item.map(|offset_minute| { |
537 | 0 | if offset_sign == b'-' { |
538 | 0 | -(offset_minute as i8) |
539 | | } else { |
540 | 0 | offset_minute as _ |
541 | | } |
542 | 0 | }) |
543 | 0 | .consume_value(|value| parsed.set_offset_minute_signed(value)) |
544 | 0 | }) |
545 | 0 | .ok_or(InvalidComponent("offset minute"))?; |
546 | | |
547 | 0 | Ok(input) |
548 | 0 | } |
549 | | |
550 | 0 | fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> { |
551 | | use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; |
552 | | use crate::parsing::combinator::{ |
553 | | any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign, |
554 | | }; |
555 | | |
556 | 0 | let dash = ascii_char::<b'-'>; |
557 | 0 | let colon = ascii_char::<b':'>; |
558 | | |
559 | 0 | let ParsedItem(input, year) = |
560 | 0 | exactly_n_digits::<u32, 4>(input).ok_or(InvalidComponent("year"))?; |
561 | 0 | let input = dash(input).ok_or(InvalidLiteral)?.into_inner(); |
562 | 0 | let ParsedItem(input, month) = |
563 | 0 | exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("month"))?; |
564 | 0 | let input = dash(input).ok_or(InvalidLiteral)?.into_inner(); |
565 | 0 | let ParsedItem(input, day) = |
566 | 0 | exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("day"))?; |
567 | 0 | let input = ascii_char_ignore_case::<b'T'>(input) |
568 | 0 | .ok_or(InvalidLiteral)? |
569 | 0 | .into_inner(); |
570 | 0 | let ParsedItem(input, hour) = |
571 | 0 | exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("hour"))?; |
572 | 0 | let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); |
573 | 0 | let ParsedItem(input, minute) = |
574 | 0 | exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("minute"))?; |
575 | 0 | let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); |
576 | 0 | let ParsedItem(input, mut second) = |
577 | 0 | exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("second"))?; |
578 | 0 | let ParsedItem(input, mut nanosecond) = |
579 | 0 | if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) { |
580 | 0 | let ParsedItem(mut input, mut value) = any_digit(input) |
581 | 0 | .ok_or(InvalidComponent("subsecond"))? |
582 | 0 | .map(|v| (v - b'0') as u32 * 100_000_000); |
583 | 0 |
|
584 | 0 | let mut multiplier = 10_000_000; |
585 | 0 | while let Some(ParsedItem(new_input, digit)) = any_digit(input) { |
586 | 0 | value += (digit - b'0') as u32 * multiplier; |
587 | 0 | input = new_input; |
588 | 0 | multiplier /= 10; |
589 | 0 | } |
590 | | |
591 | 0 | ParsedItem(input, value) |
592 | | } else { |
593 | 0 | ParsedItem(input, 0) |
594 | | }; |
595 | 0 | let ParsedItem(input, offset) = { |
596 | 0 | if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) { |
597 | 0 | ParsedItem(input, UtcOffset::UTC) |
598 | | } else { |
599 | 0 | let ParsedItem(input, offset_sign) = |
600 | 0 | sign(input).ok_or(InvalidComponent("offset hour"))?; |
601 | 0 | let ParsedItem(input, offset_hour) = |
602 | 0 | exactly_n_digits::<u8, 2>(input).ok_or(InvalidComponent("offset hour"))?; |
603 | 0 | let input = colon(input).ok_or(InvalidLiteral)?.into_inner(); |
604 | 0 | let ParsedItem(input, offset_minute) = |
605 | 0 | exactly_n_digits::<u8, 2>(input).ok_or(InvalidComponent("offset minute"))?; |
606 | | UtcOffset::from_hms( |
607 | 0 | if offset_sign == b'-' { |
608 | 0 | -(offset_hour as i8) |
609 | | } else { |
610 | 0 | offset_hour as _ |
611 | | }, |
612 | 0 | if offset_sign == b'-' { |
613 | 0 | -(offset_minute as i8) |
614 | | } else { |
615 | 0 | offset_minute as _ |
616 | | }, |
617 | | 0, |
618 | | ) |
619 | 0 | .map(|offset| ParsedItem(input, offset)) |
620 | 0 | .map_err(|mut err| { |
621 | 0 | // Provide the user a more accurate error. |
622 | 0 | if err.name == "hours" { |
623 | 0 | err.name = "offset hour"; |
624 | 0 | } else if err.name == "minutes" { |
625 | 0 | err.name = "offset minute"; |
626 | 0 | } |
627 | 0 | err |
628 | 0 | }) |
629 | 0 | .map_err(TryFromParsed::ComponentRange)? |
630 | | } |
631 | | }; |
632 | | |
633 | 0 | if !input.is_empty() { |
634 | 0 | return Err(error::Parse::UnexpectedTrailingCharacters); |
635 | 0 | } |
636 | | |
637 | | // The RFC explicitly permits leap seconds. We don't currently support them, so treat it as |
638 | | // the preceding nanosecond. However, leap seconds can only occur as the last second of the |
639 | | // month UTC. |
640 | 0 | let leap_second_input = if second == 60 { |
641 | 0 | second = 59; |
642 | 0 | nanosecond = 999_999_999; |
643 | 0 | true |
644 | | } else { |
645 | 0 | false |
646 | | }; |
647 | | |
648 | 0 | let dt = Month::from_number(month) |
649 | 0 | .and_then(|month| Date::from_calendar_date(year as _, month, day)) |
650 | 0 | .and_then(|date| date.with_hms_nano(hour, minute, second, nanosecond)) |
651 | 0 | .map(|date| date.assume_offset(offset)) |
652 | 0 | .map_err(TryFromParsed::ComponentRange)?; |
653 | | |
654 | 0 | if leap_second_input && !dt.is_valid_leap_second_stand_in() { |
655 | 0 | return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange( |
656 | 0 | error::ComponentRange { |
657 | 0 | name: "second", |
658 | 0 | minimum: 0, |
659 | 0 | maximum: 59, |
660 | 0 | value: 60, |
661 | 0 | conditional_range: true, |
662 | 0 | }, |
663 | 0 | ))); |
664 | 0 | } |
665 | 0 |
|
666 | 0 | Ok(dt) |
667 | 0 | } |
668 | | } |
669 | | |
670 | | impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> { |
671 | 0 | fn parse_into<'a>( |
672 | 0 | &self, |
673 | 0 | mut input: &'a [u8], |
674 | 0 | parsed: &mut Parsed, |
675 | 0 | ) -> Result<&'a [u8], error::Parse> { |
676 | | use crate::parsing::combinator::rfc::iso8601::ExtendedKind; |
677 | | |
678 | 0 | let mut extended_kind = ExtendedKind::Unknown; |
679 | 0 | let mut date_is_present = false; |
680 | 0 | let mut time_is_present = false; |
681 | 0 | let mut offset_is_present = false; |
682 | 0 | let mut first_error = None; |
683 | 0 |
|
684 | 0 | match Self::parse_date(parsed, &mut extended_kind)(input) { |
685 | 0 | Ok(new_input) => { |
686 | 0 | input = new_input; |
687 | 0 | date_is_present = true; |
688 | 0 | } |
689 | 0 | Err(err) => { |
690 | 0 | first_error.get_or_insert(err); |
691 | 0 | } |
692 | | } |
693 | | |
694 | 0 | match Self::parse_time(parsed, &mut extended_kind, date_is_present)(input) { |
695 | 0 | Ok(new_input) => { |
696 | 0 | input = new_input; |
697 | 0 | time_is_present = true; |
698 | 0 | } |
699 | 0 | Err(err) => { |
700 | 0 | first_error.get_or_insert(err); |
701 | 0 | } |
702 | | } |
703 | | |
704 | | // If a date and offset are present, a time must be as well. |
705 | 0 | if !date_is_present || time_is_present { |
706 | 0 | match Self::parse_offset(parsed, &mut extended_kind)(input) { |
707 | 0 | Ok(new_input) => { |
708 | 0 | input = new_input; |
709 | 0 | offset_is_present = true; |
710 | 0 | } |
711 | 0 | Err(err) => { |
712 | 0 | first_error.get_or_insert(err); |
713 | 0 | } |
714 | | } |
715 | 0 | } |
716 | | |
717 | 0 | if !date_is_present && !time_is_present && !offset_is_present { |
718 | 0 | match first_error { |
719 | 0 | Some(err) => return Err(err), |
720 | 0 | None => unreachable!("an error should be present if no components were parsed"), |
721 | | } |
722 | 0 | } |
723 | 0 |
|
724 | 0 | Ok(input) |
725 | 0 | } |
726 | | } |
727 | | // endregion well-known formats |