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