1use core::num::NonZero;
4use core::ops::Deref;
5
6use num_conv::prelude::*;
7
8use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
9use crate::error::TryFromParsed;
10#[cfg(feature = "alloc")]
11use crate::format_description::OwnedFormatItem;
12use crate::format_description::well_known::iso8601::EncodedConfig;
13use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
14use crate::format_description::{BorrowedFormatItem, FormatDescriptionV3, modifier};
15use crate::internal_macros::{bug, try_likely_ok};
16use crate::parsing::combinator::{
17 ExactlyNDigits, Sign, any_digit, ascii_char, ascii_char_ignore_case, one_or_two_digits, opt,
18 sign,
19};
20use crate::parsing::{Parsed, ParsedItem, component};
21use crate::{Date, Month, OffsetDateTime, PrivateMethod, Time, UtcOffset, error};
22
23#[cfg_attr(docsrs, doc(notable_trait))]
25#[doc(alias = "Parseable")]
26pub trait Parsable: sealed::Sealed {}
27impl Parsable for FormatDescriptionV3<'_> {}
28impl Parsable for BorrowedFormatItem<'_> {}
29impl Parsable for [BorrowedFormatItem<'_>] {}
30#[cfg(feature = "alloc")]
31impl Parsable for OwnedFormatItem {}
32#[cfg(feature = "alloc")]
33impl Parsable for [OwnedFormatItem] {}
34impl Parsable for Rfc2822 {}
35impl Parsable for Rfc3339 {}
36impl<const CONFIG: EncodedConfig> Parsable for Iso8601<CONFIG> {}
37impl<T> Parsable for T where T: Deref<Target: Parsable> {}
38
39mod sealed {
42 use super::*;
43 use crate::{PrimitiveDateTime, Timestamp, UtcDateTime};
44
45 #[expect(
47 private_interfaces,
48 reason = "not intended to be used by downstream users"
49 )]
50 pub trait Sealed {
51 fn parse_into<'a>(
55 &self,
56 input: &'a [u8],
57 parsed: &mut Parsed,
58 _: PrivateMethod,
59 ) -> Result<&'a [u8], error::Parse>;
60
61 #[deprecated(
72 since = "0.3.53",
73 note = "use the `parse` method on the target type; this method has never been part of \
74 the public API and will be removed in a future release"
75 )]
76 #[doc(hidden)]
77 fn parse(&self, input: &[u8]) -> Result<Parsed, error::Parse> {
78 self.parse_internal(input, None, PrivateMethod)
79 }
80
81 #[inline]
87 fn parse_internal(
88 &self,
89 input: &[u8],
90 defaults: Option<Parsed>,
91 _: PrivateMethod,
92 ) -> Result<Parsed, error::Parse> {
93 let mut parsed = defaults.unwrap_or_default();
94 if self
95 .parse_into(input, &mut parsed, PrivateMethod)?
96 .is_empty()
97 {
98 Ok(parsed)
99 } else {
100 Err(error::Parse::ParseFromDescription(
101 error::ParseFromDescription::UnexpectedTrailingCharacters,
102 ))
103 }
104 }
105
106 #[inline]
108 fn parse_date(
109 &self,
110 input: &[u8],
111 defaults: Option<Parsed>,
112 _: PrivateMethod,
113 ) -> Result<Date, error::Parse> {
114 Ok(self
115 .parse_internal(input, defaults, PrivateMethod)?
116 .try_into()?)
117 }
118
119 #[inline]
121 fn parse_time(
122 &self,
123 input: &[u8],
124 defaults: Option<Parsed>,
125 _: PrivateMethod,
126 ) -> Result<Time, error::Parse> {
127 Ok(self
128 .parse_internal(input, defaults, PrivateMethod)?
129 .try_into()?)
130 }
131
132 #[inline]
134 fn parse_offset(
135 &self,
136 input: &[u8],
137 defaults: Option<Parsed>,
138 _: PrivateMethod,
139 ) -> Result<UtcOffset, error::Parse> {
140 Ok(self
141 .parse_internal(input, defaults, PrivateMethod)?
142 .try_into()?)
143 }
144
145 #[inline]
147 fn parse_primitive_date_time(
148 &self,
149 input: &[u8],
150 defaults: Option<Parsed>,
151 _: PrivateMethod,
152 ) -> Result<PrimitiveDateTime, error::Parse> {
153 Ok(self
154 .parse_internal(input, defaults, PrivateMethod)?
155 .try_into()?)
156 }
157
158 #[inline]
160 fn parse_utc_date_time(
161 &self,
162 input: &[u8],
163 defaults: Option<Parsed>,
164 _: PrivateMethod,
165 ) -> Result<UtcDateTime, error::Parse> {
166 Ok(self
167 .parse_internal(input, defaults, PrivateMethod)?
168 .try_into()?)
169 }
170
171 #[inline]
173 fn parse_offset_date_time(
174 &self,
175 input: &[u8],
176 defaults: Option<Parsed>,
177 _: PrivateMethod,
178 ) -> Result<OffsetDateTime, error::Parse> {
179 Ok(self
180 .parse_internal(input, defaults, PrivateMethod)?
181 .try_into()?)
182 }
183
184 #[inline]
186 fn parse_timestamp(
187 &self,
188 input: &[u8],
189 defaults: Option<Parsed>,
190 _: PrivateMethod,
191 ) -> Result<Timestamp, error::Parse> {
192 Ok(self
193 .parse_internal(input, defaults, PrivateMethod)?
194 .try_into()?)
195 }
196 }
197}
198
199#[expect(
200 private_interfaces,
201 reason = "not intended to be used by downstream users"
202)]
203impl sealed::Sealed for FormatDescriptionV3<'_> {
204 #[inline]
205 fn parse_into<'a>(
206 &self,
207 input: &'a [u8],
208 parsed: &mut Parsed,
209 _: PrivateMethod,
210 ) -> Result<&'a [u8], error::Parse> {
211 Ok(parsed.parse_v3_inner(input, &self.inner)?)
212 }
213}
214
215#[expect(
216 private_interfaces,
217 reason = "not intended to be used by downstream users"
218)]
219impl sealed::Sealed for BorrowedFormatItem<'_> {
220 #[inline]
221 fn parse_into<'a>(
222 &self,
223 input: &'a [u8],
224 parsed: &mut Parsed,
225 _: PrivateMethod,
226 ) -> Result<&'a [u8], error::Parse> {
227 Ok(parsed.parse_item(input, self)?)
228 }
229}
230
231#[expect(
232 private_interfaces,
233 reason = "not intended to be used by downstream users"
234)]
235impl sealed::Sealed for [BorrowedFormatItem<'_>] {
236 #[inline]
237 fn parse_into<'a>(
238 &self,
239 input: &'a [u8],
240 parsed: &mut Parsed,
241 _: PrivateMethod,
242 ) -> Result<&'a [u8], error::Parse> {
243 Ok(parsed.parse_items(input, self)?)
244 }
245}
246
247#[cfg(feature = "alloc")]
248#[expect(
249 private_interfaces,
250 reason = "not intended to be used by downstream users"
251)]
252impl sealed::Sealed for OwnedFormatItem {
253 #[inline]
254 fn parse_into<'a>(
255 &self,
256 input: &'a [u8],
257 parsed: &mut Parsed,
258 _: PrivateMethod,
259 ) -> Result<&'a [u8], error::Parse> {
260 Ok(parsed.parse_item(input, self)?)
261 }
262}
263
264#[cfg(feature = "alloc")]
265#[expect(
266 private_interfaces,
267 reason = "not intended to be used by downstream users"
268)]
269impl sealed::Sealed for [OwnedFormatItem] {
270 #[inline]
271 fn parse_into<'a>(
272 &self,
273 input: &'a [u8],
274 parsed: &mut Parsed,
275 _: PrivateMethod,
276 ) -> Result<&'a [u8], error::Parse> {
277 Ok(parsed.parse_items(input, self)?)
278 }
279}
280
281#[expect(
282 private_interfaces,
283 reason = "not intended to be used by downstream users"
284)]
285impl<T> sealed::Sealed for T
286where
287 T: Deref<Target: sealed::Sealed>,
288{
289 #[inline]
290 fn parse_into<'a>(
291 &self,
292 input: &'a [u8],
293 parsed: &mut Parsed,
294 _: PrivateMethod,
295 ) -> Result<&'a [u8], error::Parse> {
296 self.deref().parse_into(input, parsed, PrivateMethod)
297 }
298}
299
300#[expect(
301 private_interfaces,
302 reason = "not intended to be used by downstream users"
303)]
304impl sealed::Sealed for Rfc2822 {
305 fn parse_into<'a>(
306 &self,
307 input: &'a [u8],
308 parsed: &mut Parsed,
309 _: PrivateMethod,
310 ) -> Result<&'a [u8], error::Parse> {
311 use crate::parsing::combinator::rfc::rfc2822::{cfws, fws, zone_literal};
312
313 let colon = ascii_char::<b':'>;
314 let comma = ascii_char::<b','>;
315
316 let input = opt(cfws)(input).into_inner();
317 let weekday = component::parse_weekday_short(
318 input,
319 modifier::WeekdayShort {
320 case_sensitive: false,
321 },
322 );
323 let input = if let Some(item) = weekday {
324 let input = try_likely_ok!(
325 item.consume_value(|value| parsed.set_weekday(value))
326 .ok_or(InvalidComponent("weekday"))
327 );
328 let input = try_likely_ok!(comma(input).ok_or(InvalidLiteral)).into_inner();
329 opt(cfws)(input).into_inner()
330 } else {
331 input
332 };
333 let input = try_likely_ok!(
334 one_or_two_digits(input)
335 .and_then(|item| item.consume_value(|value| parsed.set_day(NonZero::new(value)?)))
336 .ok_or(InvalidComponent("day"))
337 );
338 let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
339 let input = try_likely_ok!(
340 component::parse_month_short(
341 input,
342 modifier::MonthShort {
343 case_sensitive: false,
344 },
345 )
346 .and_then(|item| item.consume_value(|value| parsed.set_month(value)))
347 .ok_or(InvalidComponent("month"))
348 );
349 let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
350 let input = match ExactlyNDigits::<4>::parse(input) {
351 Some(item) => {
352 let input = try_likely_ok!(
353 item.flat_map(|year| if year >= 1900 { Some(year) } else { None })
354 .and_then(|item| {
355 item.consume_value(|value| parsed.set_year(value.cast_signed().widen()))
356 })
357 .ok_or(InvalidComponent("year"))
358 );
359 try_likely_ok!(fws(input).ok_or(InvalidLiteral)).into_inner()
360 }
361 None => {
362 let input = try_likely_ok!(
363 ExactlyNDigits::<2>::parse(input)
364 .and_then(|item| {
365 item.map(|year| year.widen::<u32>())
366 .map(|year| if year < 50 { year + 2000 } else { year + 1900 })
367 .map(|year| year.cast_signed())
368 .consume_value(|value| parsed.set_year(value))
369 })
370 .ok_or(InvalidComponent("year"))
371 );
372 try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner()
373 }
374 };
375
376 let input = try_likely_ok!(
377 ExactlyNDigits::<2>::parse(input)
378 .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
379 .ok_or(InvalidComponent("hour"))
380 );
381 let input = opt(cfws)(input).into_inner();
382 let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
383 let input = opt(cfws)(input).into_inner();
384 let input = try_likely_ok!(
385 ExactlyNDigits::<2>::parse(input)
386 .and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
387 .ok_or(InvalidComponent("minute"))
388 );
389
390 let input = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
391 let input = input.into_inner(); let input = opt(cfws)(input).into_inner();
393 let input = try_likely_ok!(
394 ExactlyNDigits::<2>::parse(input)
395 .and_then(|item| item.consume_value(|value| parsed.set_second(value)))
396 .ok_or(InvalidComponent("second"))
397 );
398 try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner()
399 } else {
400 try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner()
401 };
402
403 parsed.leap_second_allowed = true;
405
406 if let Some(zone_literal) = zone_literal(input) {
407 crate::hint::cold_path();
408 let input = try_likely_ok!(
409 zone_literal
410 .consume_value(|value| parsed.set_offset_hour(value))
411 .ok_or(InvalidComponent("offset hour"))
412 );
413 try_likely_ok!(
414 parsed
415 .set_offset_minute_signed(0)
416 .ok_or(InvalidComponent("offset minute"))
417 );
418 try_likely_ok!(
419 parsed
420 .set_offset_second_signed(0)
421 .ok_or(InvalidComponent("offset second"))
422 );
423 return Ok(input);
424 }
425
426 let ParsedItem(input, offset_sign) =
427 try_likely_ok!(sign(input).ok_or(InvalidComponent("offset hour")));
428 let input = try_likely_ok!(
429 ExactlyNDigits::<2>::parse(input)
430 .and_then(|item| {
431 item.map(|offset_hour| match offset_sign {
432 Sign::Negative => -offset_hour.cast_signed(),
433 Sign::Positive => offset_hour.cast_signed(),
434 })
435 .consume_value(|value| parsed.set_offset_hour(value))
436 })
437 .ok_or(InvalidComponent("offset hour"))
438 );
439 let input = try_likely_ok!(
440 ExactlyNDigits::<2>::parse(input)
441 .and_then(|item| {
442 item.consume_value(|value| parsed.set_offset_minute_signed(value.cast_signed()))
443 })
444 .ok_or(InvalidComponent("offset minute"))
445 );
446
447 let input = opt(cfws)(input).into_inner();
448
449 Ok(input)
450 }
451
452 fn parse_offset_date_time(
453 &self,
454 input: &[u8],
455 defaults: Option<Parsed>,
456 _: PrivateMethod,
457 ) -> Result<OffsetDateTime, error::Parse> {
458 use crate::parsing::combinator::rfc::rfc2822::{cfws, fws, zone_literal};
459
460 if let Some(mut defaults) = defaults {
461 crate::hint::cold_path();
462 return self
463 .parse_into(input, &mut defaults, PrivateMethod)
464 .and_then(|remaining| {
465 if remaining.is_empty() {
466 defaults.try_into().map_err(error::Parse::TryFromParsed)
467 } else {
468 Err(error::Parse::ParseFromDescription(
469 error::ParseFromDescription::UnexpectedTrailingCharacters,
470 ))
471 }
472 });
473 }
474
475 let colon = ascii_char::<b':'>;
476 let comma = ascii_char::<b','>;
477
478 let input = opt(cfws)(input).into_inner();
479 let weekday = component::parse_weekday_short(
480 input,
481 modifier::WeekdayShort {
482 case_sensitive: false,
483 },
484 );
485 let input = if let Some(item) = weekday {
486 let input = item.discard_value();
487 let input = try_likely_ok!(comma(input).ok_or(InvalidLiteral)).into_inner();
488 opt(cfws)(input).into_inner()
489 } else {
490 input
491 };
492 let ParsedItem(input, day) =
493 try_likely_ok!(one_or_two_digits(input).ok_or(InvalidComponent("day")));
494 let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
495 let ParsedItem(input, month) = try_likely_ok!(
496 component::parse_month_short(
497 input,
498 modifier::MonthShort {
499 case_sensitive: false,
500 },
501 )
502 .ok_or(InvalidComponent("month"))
503 );
504 let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
505 let (input, year) = match ExactlyNDigits::<4>::parse(input) {
506 Some(item) => {
507 let ParsedItem(input, year) = try_likely_ok!(
508 item.flat_map(|year| if year >= 1900 { Some(year) } else { None })
509 .ok_or(InvalidComponent("year"))
510 );
511 let input = try_likely_ok!(fws(input).ok_or(InvalidLiteral)).into_inner();
512 (input, year)
513 }
514 None => {
515 let ParsedItem(input, year) = try_likely_ok!(
516 ExactlyNDigits::<2>::parse(input)
517 .map(|item| {
518 item.map(|year| year.widen::<u16>())
519 .map(|year| if year < 50 { year + 2000 } else { year + 1900 })
520 })
521 .ok_or(InvalidComponent("year"))
522 );
523 let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
524 (input, year)
525 }
526 };
527
528 let ParsedItem(input, hour) =
529 try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("hour")));
530 let input = opt(cfws)(input).into_inner();
531 let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
532 let input = opt(cfws)(input).into_inner();
533 let ParsedItem(input, minute) =
534 try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("minute")));
535
536 let (input, mut second) = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
537 let input = input.into_inner(); let input = opt(cfws)(input).into_inner();
539 let ParsedItem(input, second) =
540 try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("second")));
541 let input = try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner();
542 (input, second)
543 } else {
544 (
545 try_likely_ok!(cfws(input).ok_or(InvalidLiteral)).into_inner(),
546 0,
547 )
548 };
549
550 let sign = sign(input);
551 let (input, offset_hour, offset_minute) = match sign {
552 None => {
553 let ParsedItem(input, offset_hour) =
554 zone_literal(input).ok_or(InvalidComponent("offset hour"))?;
555 (input, offset_hour, 0)
556 }
557 Some(ParsedItem(input, offset_sign)) => {
558 let ParsedItem(input, offset_hour) = try_likely_ok!(
559 ExactlyNDigits::<2>::parse(input)
560 .map(|item| {
561 item.map(|offset_hour| match offset_sign {
562 Sign::Negative => -offset_hour.cast_signed(),
563 Sign::Positive => offset_hour.cast_signed(),
564 })
565 })
566 .ok_or(InvalidComponent("offset hour"))
567 );
568 let ParsedItem(input, offset_minute) = try_likely_ok!(
569 ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("offset minute"))
570 );
571 (input, offset_hour, offset_minute.cast_signed())
572 }
573 };
574
575 let input = opt(cfws)(input).into_inner();
576
577 if !input.is_empty() {
578 return Err(error::Parse::ParseFromDescription(
579 error::ParseFromDescription::UnexpectedTrailingCharacters,
580 ));
581 }
582
583 let mut nanosecond = 0;
584 let leap_second_input = if second == 60 {
585 second = 59;
586 nanosecond = 999_999_999;
587 true
588 } else {
589 false
590 };
591
592 let dt = try_likely_ok!(
593 (|| {
594 let date = try_likely_ok!(Date::from_calendar_date(
595 year.cast_signed().widen(),
596 month,
597 day
598 ));
599 let time = try_likely_ok!(Time::from_hms_nano(hour, minute, second, nanosecond));
600 let offset = try_likely_ok!(UtcOffset::from_hms(offset_hour, offset_minute, 0));
601 Ok(OffsetDateTime::new_in_offset(date, time, offset))
602 })()
603 .map_err(TryFromParsed::ComponentRange)
604 );
605
606 if leap_second_input && !dt.is_valid_leap_second_stand_in() {
607 return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
608 error::ComponentRange::conditional("second"),
609 )));
610 }
611
612 Ok(dt)
613 }
614}
615
616#[expect(
617 private_interfaces,
618 reason = "not intended to be used by downstream users"
619)]
620impl sealed::Sealed for Rfc3339 {
621 fn parse_into<'a>(
622 &self,
623 input: &'a [u8],
624 parsed: &mut Parsed,
625 _: PrivateMethod,
626 ) -> Result<&'a [u8], error::Parse> {
627 let dash = ascii_char::<b'-'>;
628 let colon = ascii_char::<b':'>;
629
630 let input = try_likely_ok!(
631 ExactlyNDigits::<4>::parse(input)
632 .and_then(|item| {
633 item.consume_value(|value| parsed.set_year(value.cast_signed().widen()))
634 })
635 .ok_or(InvalidComponent("year"))
636 );
637 let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
638 let input = try_likely_ok!(
639 ExactlyNDigits::<2>::parse(input)
640 .and_then(
641 |item| item.flat_map(|value| Month::from_number(NonZero::new(value)?).ok())
642 )
643 .and_then(|item| item.consume_value(|value| parsed.set_month(value)))
644 .ok_or(InvalidComponent("month"))
645 );
646 let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
647 let input = try_likely_ok!(
648 ExactlyNDigits::<2>::parse(input)
649 .and_then(|item| item.consume_value(|value| parsed.set_day(NonZero::new(value)?)))
650 .ok_or(InvalidComponent("day"))
651 );
652
653 let input = try_likely_ok!(input.get(1..).ok_or(InvalidComponent("separator")));
661
662 let input = try_likely_ok!(
663 ExactlyNDigits::<2>::parse(input)
664 .and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
665 .ok_or(InvalidComponent("hour"))
666 );
667 let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
668 let input = try_likely_ok!(
669 ExactlyNDigits::<2>::parse(input)
670 .and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
671 .ok_or(InvalidComponent("minute"))
672 );
673 let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
674 let input = try_likely_ok!(
675 ExactlyNDigits::<2>::parse(input)
676 .and_then(|item| item.consume_value(|value| parsed.set_second(value)))
677 .ok_or(InvalidComponent("second"))
678 );
679 let input = if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
680 let ParsedItem(mut input, mut value) =
681 try_likely_ok!(any_digit(input).ok_or(InvalidComponent("subsecond")))
682 .map(|v| (v - b'0').widen::<u32>() * 100_000_000);
683
684 let mut multiplier = 10_000_000;
685 while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
686 value += (digit - b'0').widen::<u32>() * multiplier;
687 input = new_input;
688 multiplier /= 10;
689 }
690
691 try_likely_ok!(
692 parsed
693 .set_subsecond(value)
694 .ok_or(InvalidComponent("subsecond"))
695 );
696 input
697 } else {
698 input
699 };
700
701 parsed.leap_second_allowed = true;
703
704 if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
705 try_likely_ok!(
706 parsed
707 .set_offset_hour(0)
708 .ok_or(InvalidComponent("offset hour"))
709 );
710 try_likely_ok!(
711 parsed
712 .set_offset_minute_signed(0)
713 .ok_or(InvalidComponent("offset minute"))
714 );
715 try_likely_ok!(
716 parsed
717 .set_offset_second_signed(0)
718 .ok_or(InvalidComponent("offset second"))
719 );
720 return Ok(input);
721 }
722
723 let ParsedItem(input, offset_sign) =
724 try_likely_ok!(sign(input).ok_or(InvalidComponent("offset hour")));
725 let input = try_likely_ok!(
726 ExactlyNDigits::<2>::parse(input)
727 .and_then(|item| {
728 item.filter(|&offset_hour| offset_hour <= 23)?
729 .map(|offset_hour| match offset_sign {
730 Sign::Negative => -offset_hour.cast_signed(),
731 Sign::Positive => offset_hour.cast_signed(),
732 })
733 .consume_value(|value| parsed.set_offset_hour(value))
734 })
735 .ok_or(InvalidComponent("offset hour"))
736 );
737 let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
738 let input = try_likely_ok!(
739 ExactlyNDigits::<2>::parse(input)
740 .and_then(|item| {
741 item.map(|offset_minute| match offset_sign {
742 Sign::Negative => -offset_minute.cast_signed(),
743 Sign::Positive => offset_minute.cast_signed(),
744 })
745 .consume_value(|value| parsed.set_offset_minute_signed(value))
746 })
747 .ok_or(InvalidComponent("offset minute"))
748 );
749
750 Ok(input)
751 }
752
753 fn parse_offset_date_time(
754 &self,
755 input: &[u8],
756 defaults: Option<Parsed>,
757 _: PrivateMethod,
758 ) -> Result<OffsetDateTime, error::Parse> {
759 if let Some(mut defaults) = defaults {
760 crate::hint::cold_path();
761 return self
762 .parse_into(input, &mut defaults, PrivateMethod)
763 .and_then(|remaining| {
764 if remaining.is_empty() {
765 defaults.try_into().map_err(error::Parse::TryFromParsed)
766 } else {
767 Err(error::Parse::ParseFromDescription(
768 error::ParseFromDescription::UnexpectedTrailingCharacters,
769 ))
770 }
771 });
772 }
773
774 let dash = ascii_char::<b'-'>;
775 let colon = ascii_char::<b':'>;
776
777 let ParsedItem(input, year) =
778 try_likely_ok!(ExactlyNDigits::<4>::parse(input).ok_or(InvalidComponent("year")));
779 let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
780 let ParsedItem(input, month) = try_likely_ok!(
781 ExactlyNDigits::<2>::parse(input)
782 .and_then(|parsed| parsed.flat_map(NonZero::new))
783 .ok_or(InvalidComponent("month"))
784 );
785 let input = try_likely_ok!(dash(input).ok_or(InvalidLiteral)).into_inner();
786 let ParsedItem(input, day) =
787 try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("day")));
788
789 let input = try_likely_ok!(input.get(1..).ok_or(InvalidComponent("separator")));
797
798 let ParsedItem(input, hour) =
799 try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("hour")));
800 let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
801 let ParsedItem(input, minute) =
802 try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("minute")));
803 let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
804 let ParsedItem(input, mut second) =
805 try_likely_ok!(ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("second")));
806 let ParsedItem(input, mut nanosecond) =
807 if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
808 let ParsedItem(mut input, mut value) =
809 try_likely_ok!(any_digit(input).ok_or(InvalidComponent("subsecond")))
810 .map(|v| (v - b'0').widen::<u32>() * 100_000_000);
811
812 let mut multiplier = 10_000_000;
813 while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
814 value += (digit - b'0').widen::<u32>() * multiplier;
815 input = new_input;
816 multiplier /= 10;
817 }
818
819 ParsedItem(input, value)
820 } else {
821 ParsedItem(input, 0)
822 };
823 let ParsedItem(input, offset) = {
824 if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
825 ParsedItem(input, UtcOffset::UTC)
826 } else {
827 let ParsedItem(input, offset_sign) =
828 try_likely_ok!(sign(input).ok_or(InvalidComponent("offset hour")));
829 let ParsedItem(input, offset_hour) = try_likely_ok!(
830 ExactlyNDigits::<2>::parse(input)
831 .and_then(|parsed| parsed.filter(|&offset_hour| offset_hour <= 23))
832 .ok_or(InvalidComponent("offset hour"))
833 );
834 let input = try_likely_ok!(colon(input).ok_or(InvalidLiteral)).into_inner();
835 let ParsedItem(input, offset_minute) = try_likely_ok!(
836 ExactlyNDigits::<2>::parse(input).ok_or(InvalidComponent("offset minute"))
837 );
838 try_likely_ok!(
839 match offset_sign {
840 Sign::Negative => UtcOffset::from_hms(
841 -offset_hour.cast_signed(),
842 -offset_minute.cast_signed(),
843 0,
844 ),
845 Sign::Positive => UtcOffset::from_hms(
846 offset_hour.cast_signed(),
847 offset_minute.cast_signed(),
848 0,
849 ),
850 }
851 .map(|offset| ParsedItem(input, offset))
852 .map_err(TryFromParsed::ComponentRange)
853 )
854 }
855 };
856
857 if !input.is_empty() {
858 return Err(error::Parse::ParseFromDescription(
859 error::ParseFromDescription::UnexpectedTrailingCharacters,
860 ));
861 }
862
863 let leap_second_input = if second == 60 {
867 second = 59;
868 nanosecond = 999_999_999;
869 true
870 } else {
871 false
872 };
873
874 let date = try_likely_ok!(
875 Month::from_number(month)
876 .and_then(|month| Date::from_calendar_date(year.cast_signed().widen(), month, day))
877 .map_err(TryFromParsed::ComponentRange)
878 );
879 let time = try_likely_ok!(
880 Time::from_hms_nano(hour, minute, second, nanosecond)
881 .map_err(TryFromParsed::ComponentRange)
882 );
883 let dt = OffsetDateTime::new_in_offset(date, time, offset);
884
885 if leap_second_input && !dt.is_valid_leap_second_stand_in() {
886 return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
887 error::ComponentRange::conditional("second"),
888 )));
889 }
890
891 Ok(dt)
892 }
893}
894
895#[expect(
896 private_interfaces,
897 reason = "not intended to be used by downstream users"
898)]
899impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
900 #[inline]
901 fn parse_into<'a>(
902 &self,
903 mut input: &'a [u8],
904 parsed: &mut Parsed,
905 _: PrivateMethod,
906 ) -> Result<&'a [u8], error::Parse> {
907 use crate::parsing::combinator::rfc::iso8601::ExtendedKind;
908
909 let mut extended_kind = ExtendedKind::Unknown;
910 let mut date_is_present = false;
911 let mut time_is_present = false;
912 let mut offset_is_present = false;
913 let mut first_error = None;
914
915 parsed.leap_second_allowed = true;
916
917 match Self::parse_date(parsed, &mut extended_kind)(input) {
918 Ok(new_input) => {
919 input = new_input;
920 date_is_present = true;
921 }
922 Err(err) => {
923 first_error.get_or_insert(err);
924 }
925 }
926
927 match Self::parse_time(parsed, &mut extended_kind, date_is_present)(input) {
928 Ok(new_input) => {
929 input = new_input;
930 time_is_present = true;
931 }
932 Err(err) => {
933 first_error.get_or_insert(err);
934 }
935 }
936
937 if !date_is_present || time_is_present {
939 match Self::parse_offset(parsed, &mut extended_kind)(input) {
940 Ok(new_input) => {
941 input = new_input;
942 offset_is_present = true;
943 }
944 Err(err) => {
945 first_error.get_or_insert(err);
946 }
947 }
948 }
949
950 if !date_is_present && !time_is_present && !offset_is_present {
951 match first_error {
952 Some(err) => return Err(err),
953 None => bug!("an error should be present if no components were parsed"),
954 }
955 }
956
957 Ok(input)
958 }
959}