Coverage Report

Created: 2026-08-31 06:25

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