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