Source code
Revision control
Copy as Markdown
Other Tools
//! A trait that can be used to parse an item from an input.
use core::ops::Deref;
use num_conv::prelude::*;
use crate::error::TryFromParsed;
use crate::format_description::well_known::iso8601::EncodedConfig;
use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
use crate::format_description::BorrowedFormatItem;
#[cfg(feature = "alloc")]
use crate::format_description::OwnedFormatItem;
use crate::internal_macros::bug;
use crate::parsing::{Parsed, ParsedItem};
use crate::{error, Date, Month, OffsetDateTime, Time, UtcOffset, Weekday};
/// A type that can be parsed.
#[cfg_attr(__time_03_docs, doc(notable_trait))]
#[doc(alias = "Parseable")]
pub trait Parsable: sealed::Sealed {}
impl Parsable for BorrowedFormatItem<'_> {}
impl Parsable for [BorrowedFormatItem<'_>] {}
#[cfg(feature = "alloc")]
impl Parsable for OwnedFormatItem {}
#[cfg(feature = "alloc")]
impl Parsable for [OwnedFormatItem] {}
impl Parsable for Rfc2822 {}
impl Parsable for Rfc3339 {}
impl<const CONFIG: EncodedConfig> Parsable for Iso8601<CONFIG> {}
impl<T: Deref> Parsable for T where T::Target: Parsable {}
/// Seal the trait to prevent downstream users from implementing it, while still allowing it to
/// exist in generic bounds.
mod sealed {
#[allow(clippy::wildcard_imports)]
use super::*;
use crate::PrimitiveDateTime;
/// Parse the item using a format description and an input.
pub trait Sealed {
/// Parse the item into the provided [`Parsed`] struct.
///
/// This method can be used to parse a single component without parsing the full value.
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
) -> Result<&'a [u8], error::Parse>;
/// Parse the item into a new [`Parsed`] struct.
///
/// This method can only be used to parse a complete value of a type. If any characters
/// remain after parsing, an error will be returned.
fn parse(&self, input: &[u8]) -> Result<Parsed, error::Parse> {
let mut parsed = Parsed::new();
if self.parse_into(input, &mut parsed)?.is_empty() {
Ok(parsed)
} else {
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters,
))
}
}
/// Parse a [`Date`] from the format description.
fn parse_date(&self, input: &[u8]) -> Result<Date, error::Parse> {
Ok(self.parse(input)?.try_into()?)
}
/// Parse a [`Time`] from the format description.
fn parse_time(&self, input: &[u8]) -> Result<Time, error::Parse> {
Ok(self.parse(input)?.try_into()?)
}
/// Parse a [`UtcOffset`] from the format description.
fn parse_offset(&self, input: &[u8]) -> Result<UtcOffset, error::Parse> {
Ok(self.parse(input)?.try_into()?)
}
/// Parse a [`PrimitiveDateTime`] from the format description.
fn parse_primitive_date_time(
&self,
input: &[u8],
) -> Result<PrimitiveDateTime, error::Parse> {
Ok(self.parse(input)?.try_into()?)
}
/// Parse a [`OffsetDateTime`] from the format description.
fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
Ok(self.parse(input)?.try_into()?)
}
}
}
// region: custom formats
impl sealed::Sealed for BorrowedFormatItem<'_> {
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
) -> Result<&'a [u8], error::Parse> {
Ok(parsed.parse_item(input, self)?)
}
}
impl sealed::Sealed for [BorrowedFormatItem<'_>] {
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
) -> Result<&'a [u8], error::Parse> {
Ok(parsed.parse_items(input, self)?)
}
}
#[cfg(feature = "alloc")]
impl sealed::Sealed for OwnedFormatItem {
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
) -> Result<&'a [u8], error::Parse> {
Ok(parsed.parse_item(input, self)?)
}
}
#[cfg(feature = "alloc")]
impl sealed::Sealed for [OwnedFormatItem] {
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
) -> Result<&'a [u8], error::Parse> {
Ok(parsed.parse_items(input, self)?)
}
}
impl<T: Deref> sealed::Sealed for T
where
T::Target: sealed::Sealed,
{
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
) -> Result<&'a [u8], error::Parse> {
self.deref().parse_into(input, parsed)
}
}
// endregion custom formats
// region: well-known formats
impl sealed::Sealed for Rfc2822 {
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
) -> Result<&'a [u8], error::Parse> {
use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
use crate::parsing::combinator::rfc::rfc2822::{cfws, fws};
use crate::parsing::combinator::{
ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign,
};
let colon = ascii_char::<b':'>;
let comma = ascii_char::<b','>;
let input = opt(cfws)(input).into_inner();
let weekday = first_match(
[
(b"Mon".as_slice(), Weekday::Monday),
(b"Tue".as_slice(), Weekday::Tuesday),
(b"Wed".as_slice(), Weekday::Wednesday),
(b"Thu".as_slice(), Weekday::Thursday),
(b"Fri".as_slice(), Weekday::Friday),
(b"Sat".as_slice(), Weekday::Saturday),
(b"Sun".as_slice(), Weekday::Sunday),
],
false,
)(input);
let input = if let Some(item) = weekday {
let input = item
.consume_value(|value| parsed.set_weekday(value))
.ok_or(InvalidComponent("weekday"))?;
let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
opt(cfws)(input).into_inner()
} else {
input
};
let input = n_to_m_digits::<1, 2, _>(input)
.and_then(|item| item.consume_value(|value| parsed.set_day(value)))
.ok_or(InvalidComponent("day"))?;
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
let input = first_match(
[
(b"Jan".as_slice(), Month::January),
(b"Feb".as_slice(), Month::February),
(b"Mar".as_slice(), Month::March),
(b"Apr".as_slice(), Month::April),
(b"May".as_slice(), Month::May),
(b"Jun".as_slice(), Month::June),
(b"Jul".as_slice(), Month::July),
(b"Aug".as_slice(), Month::August),
(b"Sep".as_slice(), Month::September),
(b"Oct".as_slice(), Month::October),
(b"Nov".as_slice(), Month::November),
(b"Dec".as_slice(), Month::December),
],
false,
)(input)
.and_then(|item| item.consume_value(|value| parsed.set_month(value)))
.ok_or(InvalidComponent("month"))?;
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
let input = match exactly_n_digits::<4, u32>(input) {
Some(item) => {
let input = item
.flat_map(|year| if year >= 1900 { Some(year) } else { None })
.and_then(|item| {
item.consume_value(|value| parsed.set_year(value.cast_signed()))
})
.ok_or(InvalidComponent("year"))?;
fws(input).ok_or(InvalidLiteral)?.into_inner()
}
None => {
let input = exactly_n_digits::<2, u32>(input)
.and_then(|item| {
item.map(|year| if year < 50 { year + 2000 } else { year + 1900 })
.map(|year| year.cast_signed())
.consume_value(|value| parsed.set_year(value))
})
.ok_or(InvalidComponent("year"))?;
cfws(input).ok_or(InvalidLiteral)?.into_inner()
}
};
let input = exactly_n_digits::<2, _>(input)
.and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
.ok_or(InvalidComponent("hour"))?;
let input = opt(cfws)(input).into_inner();
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
let input = opt(cfws)(input).into_inner();
let input = exactly_n_digits::<2, _>(input)
.and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
.ok_or(InvalidComponent("minute"))?;
let input = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
let input = input.into_inner(); // discard the colon
let input = opt(cfws)(input).into_inner();
let input = exactly_n_digits::<2, _>(input)
.and_then(|item| item.consume_value(|value| parsed.set_second(value)))
.ok_or(InvalidComponent("second"))?;
cfws(input).ok_or(InvalidLiteral)?.into_inner()
} else {
cfws(input).ok_or(InvalidLiteral)?.into_inner()
};
// The RFC explicitly allows leap seconds.
parsed.leap_second_allowed = true;
#[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522
let zone_literal = first_match(
[
(b"UT".as_slice(), 0),
(b"GMT".as_slice(), 0),
(b"EST".as_slice(), -5),
(b"EDT".as_slice(), -4),
(b"CST".as_slice(), -6),
(b"CDT".as_slice(), -5),
(b"MST".as_slice(), -7),
(b"MDT".as_slice(), -6),
(b"PST".as_slice(), -8),
(b"PDT".as_slice(), -7),
],
false,
)(input)
.or_else(|| match input {
[
b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z',
rest @ ..,
] => Some(ParsedItem(rest, 0)),
_ => None,
});
if let Some(zone_literal) = zone_literal {
let input = zone_literal
.consume_value(|value| parsed.set_offset_hour(value))
.ok_or(InvalidComponent("offset hour"))?;
parsed
.set_offset_minute_signed(0)
.ok_or(InvalidComponent("offset minute"))?;
parsed
.set_offset_second_signed(0)
.ok_or(InvalidComponent("offset second"))?;
return Ok(input);
}
let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
let input = exactly_n_digits::<2, u8>(input)
.and_then(|item| {
item.map(|offset_hour| {
if offset_sign == b'-' {
-offset_hour.cast_signed()
} else {
offset_hour.cast_signed()
}
})
.consume_value(|value| parsed.set_offset_hour(value))
})
.ok_or(InvalidComponent("offset hour"))?;
let input = exactly_n_digits::<2, u8>(input)
.and_then(|item| {
item.consume_value(|value| parsed.set_offset_minute_signed(value.cast_signed()))
})
.ok_or(InvalidComponent("offset minute"))?;
let input = opt(cfws)(input).into_inner();
Ok(input)
}
fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
use crate::parsing::combinator::rfc::rfc2822::{cfws, fws};
use crate::parsing::combinator::{
ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign,
};
let colon = ascii_char::<b':'>;
let comma = ascii_char::<b','>;
let input = opt(cfws)(input).into_inner();
// This parses the weekday, but we don't actually use the value anywhere. Because of this,
// just return `()` to avoid unnecessary generated code.
let weekday = first_match(
[
(b"Mon".as_slice(), ()),
(b"Tue".as_slice(), ()),
(b"Wed".as_slice(), ()),
(b"Thu".as_slice(), ()),
(b"Fri".as_slice(), ()),
(b"Sat".as_slice(), ()),
(b"Sun".as_slice(), ()),
],
false,
)(input);
let input = if let Some(item) = weekday {
let input = item.into_inner();
let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
opt(cfws)(input).into_inner()
} else {
input
};
let ParsedItem(input, day) =
n_to_m_digits::<1, 2, _>(input).ok_or(InvalidComponent("day"))?;
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
let ParsedItem(input, month) = first_match(
[
(b"Jan".as_slice(), Month::January),
(b"Feb".as_slice(), Month::February),
(b"Mar".as_slice(), Month::March),
(b"Apr".as_slice(), Month::April),
(b"May".as_slice(), Month::May),
(b"Jun".as_slice(), Month::June),
(b"Jul".as_slice(), Month::July),
(b"Aug".as_slice(), Month::August),
(b"Sep".as_slice(), Month::September),
(b"Oct".as_slice(), Month::October),
(b"Nov".as_slice(), Month::November),
(b"Dec".as_slice(), Month::December),
],
false,
)(input)
.ok_or(InvalidComponent("month"))?;
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
let (input, year) = match exactly_n_digits::<4, u32>(input) {
Some(item) => {
let ParsedItem(input, year) = item
.flat_map(|year| if year >= 1900 { Some(year) } else { None })
.ok_or(InvalidComponent("year"))?;
let input = fws(input).ok_or(InvalidLiteral)?.into_inner();
(input, year)
}
None => {
let ParsedItem(input, year) = exactly_n_digits::<2, u32>(input)
.map(|item| item.map(|year| if year < 50 { year + 2000 } else { year + 1900 }))
.ok_or(InvalidComponent("year"))?;
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
(input, year)
}
};
let ParsedItem(input, hour) =
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("hour"))?;
let input = opt(cfws)(input).into_inner();
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
let input = opt(cfws)(input).into_inner();
let ParsedItem(input, minute) =
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("minute"))?;
let (input, mut second) = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
let input = input.into_inner(); // discard the colon
let input = opt(cfws)(input).into_inner();
let ParsedItem(input, second) =
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("second"))?;
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
(input, second)
} else {
(cfws(input).ok_or(InvalidLiteral)?.into_inner(), 0)
};
#[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522
let zone_literal = first_match(
[
(b"UT".as_slice(), 0),
(b"GMT".as_slice(), 0),
(b"EST".as_slice(), -5),
(b"EDT".as_slice(), -4),
(b"CST".as_slice(), -6),
(b"CDT".as_slice(), -5),
(b"MST".as_slice(), -7),
(b"MDT".as_slice(), -6),
(b"PST".as_slice(), -8),
(b"PDT".as_slice(), -7),
],
false,
)(input)
.or_else(|| match input {
[
b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z',
rest @ ..,
] => Some(ParsedItem(rest, 0)),
_ => None,
});
let (input, offset_hour, offset_minute) = if let Some(zone_literal) = zone_literal {
let ParsedItem(input, offset_hour) = zone_literal;
(input, offset_hour, 0)
} else {
let ParsedItem(input, offset_sign) =
sign(input).ok_or(InvalidComponent("offset hour"))?;
let ParsedItem(input, offset_hour) = exactly_n_digits::<2, u8>(input)
.map(|item| {
item.map(|offset_hour| {
if offset_sign == b'-' {
-offset_hour.cast_signed()
} else {
offset_hour.cast_signed()
}
})
})
.ok_or(InvalidComponent("offset hour"))?;
let ParsedItem(input, offset_minute) =
exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?;
(input, offset_hour, offset_minute.cast_signed())
};
let input = opt(cfws)(input).into_inner();
if !input.is_empty() {
return Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters,
));
}
let mut nanosecond = 0;
let leap_second_input = if second == 60 {
second = 59;
nanosecond = 999_999_999;
true
} else {
false
};
let dt = (|| {
let date = Date::from_calendar_date(year.cast_signed(), month, day)?;
let time = Time::from_hms_nano(hour, minute, second, nanosecond)?;
let offset = UtcOffset::from_hms(offset_hour, offset_minute, 0)?;
Ok(OffsetDateTime::new_in_offset(date, time, offset))
})()
.map_err(TryFromParsed::ComponentRange)?;
if leap_second_input && !dt.is_valid_leap_second_stand_in() {
return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
error::ComponentRange {
name: "second",
minimum: 0,
maximum: 59,
value: 60,
conditional_range: true,
},
)));
}
Ok(dt)
}
}
impl sealed::Sealed for Rfc3339 {
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
) -> Result<&'a [u8], error::Parse> {
use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
use crate::parsing::combinator::{
any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign,
};
let dash = ascii_char::<b'-'>;
let colon = ascii_char::<b':'>;
let input = exactly_n_digits::<4, u32>(input)
.and_then(|item| item.consume_value(|value| parsed.set_year(value.cast_signed())))
.ok_or(InvalidComponent("year"))?;
let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
let input = exactly_n_digits::<2, _>(input)
.and_then(|item| item.flat_map(|value| Month::from_number(value).ok()))
.and_then(|item| item.consume_value(|value| parsed.set_month(value)))
.ok_or(InvalidComponent("month"))?;
let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
let input = exactly_n_digits::<2, _>(input)
.and_then(|item| item.consume_value(|value| parsed.set_day(value)))
.ok_or(InvalidComponent("day"))?;
let input = ascii_char_ignore_case::<b'T'>(input)
.ok_or(InvalidLiteral)?
.into_inner();
let input = exactly_n_digits::<2, _>(input)
.and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
.ok_or(InvalidComponent("hour"))?;
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
let input = exactly_n_digits::<2, _>(input)
.and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
.ok_or(InvalidComponent("minute"))?;
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
let input = exactly_n_digits::<2, _>(input)
.and_then(|item| item.consume_value(|value| parsed.set_second(value)))
.ok_or(InvalidComponent("second"))?;
let input = if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
let ParsedItem(mut input, mut value) = any_digit(input)
.ok_or(InvalidComponent("subsecond"))?
.map(|v| (v - b'0').extend::<u32>() * 100_000_000);
let mut multiplier = 10_000_000;
while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
value += (digit - b'0').extend::<u32>() * multiplier;
input = new_input;
multiplier /= 10;
}
parsed
.set_subsecond(value)
.ok_or(InvalidComponent("subsecond"))?;
input
} else {
input
};
// The RFC explicitly allows leap seconds.
parsed.leap_second_allowed = true;
if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
parsed
.set_offset_hour(0)
.ok_or(InvalidComponent("offset hour"))?;
parsed
.set_offset_minute_signed(0)
.ok_or(InvalidComponent("offset minute"))?;
parsed
.set_offset_second_signed(0)
.ok_or(InvalidComponent("offset second"))?;
return Ok(input);
}
let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
let input = exactly_n_digits::<2, u8>(input)
.and_then(|item| {
item.filter(|&offset_hour| offset_hour <= 23)?
.map(|offset_hour| {
if offset_sign == b'-' {
-offset_hour.cast_signed()
} else {
offset_hour.cast_signed()
}
})
.consume_value(|value| parsed.set_offset_hour(value))
})
.ok_or(InvalidComponent("offset hour"))?;
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
let input = exactly_n_digits::<2, u8>(input)
.and_then(|item| {
item.map(|offset_minute| {
if offset_sign == b'-' {
-offset_minute.cast_signed()
} else {
offset_minute.cast_signed()
}
})
.consume_value(|value| parsed.set_offset_minute_signed(value))
})
.ok_or(InvalidComponent("offset minute"))?;
Ok(input)
}
fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
use crate::parsing::combinator::{
any_digit, ascii_char, ascii_char_ignore_case, exactly_n_digits, sign,
};
let dash = ascii_char::<b'-'>;
let colon = ascii_char::<b':'>;
let ParsedItem(input, year) =
exactly_n_digits::<4, u32>(input).ok_or(InvalidComponent("year"))?;
let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
let ParsedItem(input, month) =
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("month"))?;
let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
let ParsedItem(input, day) =
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("day"))?;
let input = ascii_char_ignore_case::<b'T'>(input)
.ok_or(InvalidLiteral)?
.into_inner();
let ParsedItem(input, hour) =
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("hour"))?;
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
let ParsedItem(input, minute) =
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("minute"))?;
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
let ParsedItem(input, mut second) =
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("second"))?;
let ParsedItem(input, mut nanosecond) =
if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
let ParsedItem(mut input, mut value) = any_digit(input)
.ok_or(InvalidComponent("subsecond"))?
.map(|v| (v - b'0').extend::<u32>() * 100_000_000);
let mut multiplier = 10_000_000;
while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
value += (digit - b'0').extend::<u32>() * multiplier;
input = new_input;
multiplier /= 10;
}
ParsedItem(input, value)
} else {
ParsedItem(input, 0)
};
let ParsedItem(input, offset) = {
if let Some(ParsedItem(input, ())) = ascii_char_ignore_case::<b'Z'>(input) {
ParsedItem(input, UtcOffset::UTC)
} else {
let ParsedItem(input, offset_sign) =
sign(input).ok_or(InvalidComponent("offset hour"))?;
let ParsedItem(input, offset_hour) = exactly_n_digits::<2, u8>(input)
.and_then(|parsed| parsed.filter(|&offset_hour| offset_hour <= 23))
.ok_or(InvalidComponent("offset hour"))?;
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
let ParsedItem(input, offset_minute) =
exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?;
UtcOffset::from_hms(
if offset_sign == b'-' {
-offset_hour.cast_signed()
} else {
offset_hour.cast_signed()
},
if offset_sign == b'-' {
-offset_minute.cast_signed()
} else {
offset_minute.cast_signed()
},
0,
)
.map(|offset| ParsedItem(input, offset))
.map_err(|mut err| {
// Provide the user a more accurate error.
if err.name == "hours" {
err.name = "offset hour";
} else if err.name == "minutes" {
err.name = "offset minute";
}
err
})
.map_err(TryFromParsed::ComponentRange)?
}
};
if !input.is_empty() {
return Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters,
));
}
// The RFC explicitly permits leap seconds. We don't currently support them, so treat it as
// the preceding nanosecond. However, leap seconds can only occur as the last second of the
// month UTC.
let leap_second_input = if second == 60 {
second = 59;
nanosecond = 999_999_999;
true
} else {
false
};
let date = Month::from_number(month)
.and_then(|month| Date::from_calendar_date(year.cast_signed(), month, day))
.map_err(TryFromParsed::ComponentRange)?;
let time = Time::from_hms_nano(hour, minute, second, nanosecond)
.map_err(TryFromParsed::ComponentRange)?;
let dt = OffsetDateTime::new_in_offset(date, time, offset);
if leap_second_input && !dt.is_valid_leap_second_stand_in() {
return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
error::ComponentRange {
name: "second",
minimum: 0,
maximum: 59,
value: 60,
conditional_range: true,
},
)));
}
Ok(dt)
}
}
impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
fn parse_into<'a>(
&self,
mut input: &'a [u8],
parsed: &mut Parsed,
) -> Result<&'a [u8], error::Parse> {
use crate::parsing::combinator::rfc::iso8601::ExtendedKind;
let mut extended_kind = ExtendedKind::Unknown;
let mut date_is_present = false;
let mut time_is_present = false;
let mut offset_is_present = false;
let mut first_error = None;
parsed.leap_second_allowed = true;
match Self::parse_date(parsed, &mut extended_kind)(input) {
Ok(new_input) => {
input = new_input;
date_is_present = true;
}
Err(err) => {
first_error.get_or_insert(err);
}
}
match Self::parse_time(parsed, &mut extended_kind, date_is_present)(input) {
Ok(new_input) => {
input = new_input;
time_is_present = true;
}
Err(err) => {
first_error.get_or_insert(err);
}
}
// If a date and offset are present, a time must be as well.
if !date_is_present || time_is_present {
match Self::parse_offset(parsed, &mut extended_kind)(input) {
Ok(new_input) => {
input = new_input;
offset_is_present = true;
}
Err(err) => {
first_error.get_or_insert(err);
}
}
}
if !date_is_present && !time_is_present && !offset_is_present {
match first_error {
Some(err) => return Err(err),
None => bug!("an error should be present if no components were parsed"),
}
}
Ok(input)
}
}
// endregion well-known formats