use core::convert::TryInto;
use core::ops::Deref;
use crate::error::TryFromParsed;
use crate::format_description::well_known::Rfc3339;
use crate::format_description::FormatItem;
use crate::parsing::{strip_prefix, Parsed, ParsedItem};
use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset};
pub trait Parsable: sealed::Sealed {}
impl Parsable for FormatItem<'_> {}
impl Parsable for [FormatItem<'_>] {}
impl Parsable for Rfc3339 {}
#[allow(clippy::use_self)]
impl<T: Deref> Parsable for T where T::Target: Parsable {}
mod sealed {
#[allow(clippy::wildcard_imports)]
use super::*;
#[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
pub trait Sealed {
fn parse_into<'a>(
&self,
input: &'a [u8],
parsed: &mut Parsed,
) -> Result<&'a [u8], error::Parse>;
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::UnexpectedTrailingCharacters)
}
}
fn parse_date(&self, input: &[u8]) -> Result<Date, error::Parse> {
Ok(self.parse(input)?.try_into()?)
}
fn parse_time(&self, input: &[u8]) -> Result<Time, error::Parse> {
Ok(self.parse(input)?.try_into()?)
}
fn parse_offset(&self, input: &[u8]) -> Result<UtcOffset, error::Parse> {
Ok(self.parse(input)?.try_into()?)
}
fn parse_date_time(&self, input: &[u8]) -> Result<PrimitiveDateTime, error::Parse> {
Ok(self.parse(input)?.try_into()?)
}
fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
Ok(self.parse(input)?.try_into()?)
}
}
}
impl sealed::Sealed for FormatItem<'_> {
fn parse_into<'a>(
&self,
mut input: &'a [u8],
parsed: &mut Parsed,
) -> Result<&'a [u8], error::Parse> {
match self {
Self::Literal(literal) => {
input = strip_prefix(input, *literal)
.ok_or(error::ParseFromDescription::InvalidLiteral)?;
}
Self::Component(component) => input = parsed.parse_component(input, *component)?,
Self::Compound(compound) => input = compound.parse_into(input, parsed)?,
}
Ok(input)
}
}
impl sealed::Sealed for [FormatItem<'_>] {
fn parse_into<'a>(
&self,
mut input: &'a [u8],
parsed: &mut Parsed,
) -> Result<&'a [u8], error::Parse> {
for item in self.iter() {
input = item.parse_into(input, parsed)?;
}
Ok(input)
}
}
#[allow(clippy::use_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)
}
}
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)(input)
.ok_or(InvalidComponent("year"))?
.map(|year: u32| year as _)
.assign_value_to(&mut parsed.year);
let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
let input = exactly_n_digits(2)(input)
.ok_or(InvalidComponent("month"))?
.flat_map_res(Month::from_number)
.map_err(error::TryFromParsed::ComponentRange)?
.assign_value_to(&mut parsed.month);
let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
let input = exactly_n_digits(2)(input)
.ok_or(InvalidComponent("day"))?
.assign_value_to(&mut parsed.day);
let input = ascii_char_ignore_case(b'T')(input)
.ok_or(InvalidLiteral)?
.into_inner();
let input = exactly_n_digits(2)(input)
.ok_or(InvalidComponent("hour"))?
.assign_value_to(&mut parsed.hour_24);
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
let input = exactly_n_digits(2)(input)
.ok_or(InvalidComponent("minute"))?
.assign_value_to(&mut parsed.minute);
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
let input = exactly_n_digits(2)(input)
.ok_or(InvalidComponent("second"))?
.map(|second|
if second == 60 { 59 } else { second }
)
.assign_value_to(&mut parsed.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') as u32 * 100_000_000);
let mut multiplier = 10_000_000;
while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
value += (digit - b'0') as u32 * multiplier;
input = new_input;
multiplier /= 10;
}
ParsedItem(input, value).assign_value_to(&mut parsed.subsecond)
} else {
input
};
if let Some(ParsedItem(input, ())) = ascii_char_ignore_case(b'Z')(input) {
parsed.offset_hour = Some(0);
parsed.offset_minute = Some(0);
parsed.offset_second = Some(0);
return Ok(input);
}
let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset_hour"))?;
let input = exactly_n_digits(2)(input)
.ok_or(InvalidComponent("offset_hour"))?
.map(|offset_hour: u8| {
if offset_sign == b'-' {
-(offset_hour as i8)
} else {
offset_hour as _
}
})
.assign_value_to(&mut parsed.offset_hour);
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
let input = exactly_n_digits(2)(input)
.ok_or(InvalidComponent("offset_minute"))?
.assign_value_to(&mut parsed.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::<u32>(4)(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, second) = exactly_n_digits(2)(input)
.ok_or(InvalidComponent("second"))?
.map(|seconds| if seconds == 60 { 59 } else { seconds });
let ParsedItem(input, 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') as u32 * 100_000_000);
let mut multiplier = 10_000_000;
while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
value += (digit - b'0') as 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::<u8>(2)(input).ok_or(InvalidComponent("offset_hour"))?;
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
let ParsedItem(input, offset_minute) =
exactly_n_digits::<u8>(2)(input).ok_or(InvalidComponent("offset_minute"))?;
UtcOffset::from_hms(
if offset_sign == b'-' {
-(offset_hour as i8)
} else {
offset_hour as _
},
offset_minute as _,
0,
)
.map(|offset| ParsedItem(input, offset))
.map_err(TryFromParsed::ComponentRange)?
}
};
if !input.is_empty() {
return Err(error::Parse::UnexpectedTrailingCharacters);
}
Ok(Month::from_number(month)
.and_then(|month| Date::from_calendar_date(year as _, month, day))
.and_then(|date| date.with_hms_nano(hour, minute, second, nanosecond))
.map(|date| date.assume_offset(offset))
.map_err(TryFromParsed::ComponentRange)?)
}
}