Source code
Revision control
Copy as Markdown
Other Tools
//! Parsing implementations for all [`Component`](crate::format_description::Component)s.
use core::num::{NonZeroU16, NonZeroU8};
use num_conv::prelude::*;
use crate::convert::*;
use crate::format_description::modifier;
#[cfg(feature = "large-dates")]
use crate::parsing::combinator::n_to_m_digits_padded;
use crate::parsing::combinator::{
any_digit, exactly_n_digits, exactly_n_digits_padded, first_match, n_to_m_digits, opt, sign,
};
use crate::parsing::ParsedItem;
use crate::{Month, Weekday};
// region: date components
/// Parse the "year" component of a `Date`.
pub(crate) fn parse_year(input: &[u8], modifiers: modifier::Year) -> Option<ParsedItem<'_, i32>> {
match modifiers.repr {
modifier::YearRepr::Full => {
let ParsedItem(input, sign) = opt(sign)(input);
#[cfg(not(feature = "large-dates"))]
let ParsedItem(input, year) =
exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
#[cfg(feature = "large-dates")]
let ParsedItem(input, year) =
n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?;
match sign {
Some(b'-') => Some(ParsedItem(input, -year.cast_signed())),
None if modifiers.sign_is_mandatory || year >= 10_000 => None,
_ => Some(ParsedItem(input, year.cast_signed())),
}
}
modifier::YearRepr::LastTwo => Some(
exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?.map(|v| v.cast_signed()),
),
}
}
/// Parse the "month" component of a `Date`.
pub(crate) fn parse_month(
input: &[u8],
modifiers: modifier::Month,
) -> Option<ParsedItem<'_, Month>> {
use Month::*;
let ParsedItem(remaining, value) = first_match(
match modifiers.repr {
modifier::MonthRepr::Numerical => {
return exactly_n_digits_padded::<2, _>(modifiers.padding)(input)?
.flat_map(|n| Month::from_number(n).ok());
}
modifier::MonthRepr::Long => [
(b"January".as_slice(), January),
(b"February".as_slice(), February),
(b"March".as_slice(), March),
(b"April".as_slice(), April),
(b"May".as_slice(), May),
(b"June".as_slice(), June),
(b"July".as_slice(), July),
(b"August".as_slice(), August),
(b"September".as_slice(), September),
(b"October".as_slice(), October),
(b"November".as_slice(), November),
(b"December".as_slice(), December),
],
modifier::MonthRepr::Short => [
(b"Jan".as_slice(), January),
(b"Feb".as_slice(), February),
(b"Mar".as_slice(), March),
(b"Apr".as_slice(), April),
(b"May".as_slice(), May),
(b"Jun".as_slice(), June),
(b"Jul".as_slice(), July),
(b"Aug".as_slice(), August),
(b"Sep".as_slice(), September),
(b"Oct".as_slice(), October),
(b"Nov".as_slice(), November),
(b"Dec".as_slice(), December),
],
},
modifiers.case_sensitive,
)(input)?;
Some(ParsedItem(remaining, value))
}
/// Parse the "week number" component of a `Date`.
pub(crate) fn parse_week_number(
input: &[u8],
modifiers: modifier::WeekNumber,
) -> Option<ParsedItem<'_, u8>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
}
/// Parse the "weekday" component of a `Date`.
pub(crate) fn parse_weekday(
input: &[u8],
modifiers: modifier::Weekday,
) -> Option<ParsedItem<'_, Weekday>> {
first_match(
match (modifiers.repr, modifiers.one_indexed) {
(modifier::WeekdayRepr::Short, _) => [
(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),
],
(modifier::WeekdayRepr::Long, _) => [
(b"Monday".as_slice(), Weekday::Monday),
(b"Tuesday".as_slice(), Weekday::Tuesday),
(b"Wednesday".as_slice(), Weekday::Wednesday),
(b"Thursday".as_slice(), Weekday::Thursday),
(b"Friday".as_slice(), Weekday::Friday),
(b"Saturday".as_slice(), Weekday::Saturday),
(b"Sunday".as_slice(), Weekday::Sunday),
],
(modifier::WeekdayRepr::Sunday, false) => [
(b"1".as_slice(), Weekday::Monday),
(b"2".as_slice(), Weekday::Tuesday),
(b"3".as_slice(), Weekday::Wednesday),
(b"4".as_slice(), Weekday::Thursday),
(b"5".as_slice(), Weekday::Friday),
(b"6".as_slice(), Weekday::Saturday),
(b"0".as_slice(), Weekday::Sunday),
],
(modifier::WeekdayRepr::Sunday, true) => [
(b"2".as_slice(), Weekday::Monday),
(b"3".as_slice(), Weekday::Tuesday),
(b"4".as_slice(), Weekday::Wednesday),
(b"5".as_slice(), Weekday::Thursday),
(b"6".as_slice(), Weekday::Friday),
(b"7".as_slice(), Weekday::Saturday),
(b"1".as_slice(), Weekday::Sunday),
],
(modifier::WeekdayRepr::Monday, false) => [
(b"0".as_slice(), Weekday::Monday),
(b"1".as_slice(), Weekday::Tuesday),
(b"2".as_slice(), Weekday::Wednesday),
(b"3".as_slice(), Weekday::Thursday),
(b"4".as_slice(), Weekday::Friday),
(b"5".as_slice(), Weekday::Saturday),
(b"6".as_slice(), Weekday::Sunday),
],
(modifier::WeekdayRepr::Monday, true) => [
(b"1".as_slice(), Weekday::Monday),
(b"2".as_slice(), Weekday::Tuesday),
(b"3".as_slice(), Weekday::Wednesday),
(b"4".as_slice(), Weekday::Thursday),
(b"5".as_slice(), Weekday::Friday),
(b"6".as_slice(), Weekday::Saturday),
(b"7".as_slice(), Weekday::Sunday),
],
},
modifiers.case_sensitive,
)(input)
}
/// Parse the "ordinal" component of a `Date`.
pub(crate) fn parse_ordinal(
input: &[u8],
modifiers: modifier::Ordinal,
) -> Option<ParsedItem<'_, NonZeroU16>> {
exactly_n_digits_padded::<3, _>(modifiers.padding)(input)
}
/// Parse the "day" component of a `Date`.
pub(crate) fn parse_day(
input: &[u8],
modifiers: modifier::Day,
) -> Option<ParsedItem<'_, NonZeroU8>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
}
// endregion date components
// region: time components
/// Indicate whether the hour is "am" or "pm".
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Period {
#[allow(clippy::missing_docs_in_private_items)]
Am,
#[allow(clippy::missing_docs_in_private_items)]
Pm,
}
/// Parse the "hour" component of a `Time`.
pub(crate) fn parse_hour(input: &[u8], modifiers: modifier::Hour) -> Option<ParsedItem<'_, u8>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
}
/// Parse the "minute" component of a `Time`.
pub(crate) fn parse_minute(
input: &[u8],
modifiers: modifier::Minute,
) -> Option<ParsedItem<'_, u8>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
}
/// Parse the "second" component of a `Time`.
pub(crate) fn parse_second(
input: &[u8],
modifiers: modifier::Second,
) -> Option<ParsedItem<'_, u8>> {
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
}
/// Parse the "period" component of a `Time`. Required if the hour is on a 12-hour clock.
pub(crate) fn parse_period(
input: &[u8],
modifiers: modifier::Period,
) -> Option<ParsedItem<'_, Period>> {
first_match(
if modifiers.is_uppercase {
[
(b"AM".as_slice(), Period::Am),
(b"PM".as_slice(), Period::Pm),
]
} else {
[
(b"am".as_slice(), Period::Am),
(b"pm".as_slice(), Period::Pm),
]
},
modifiers.case_sensitive,
)(input)
}
/// Parse the "subsecond" component of a `Time`.
pub(crate) fn parse_subsecond(
input: &[u8],
modifiers: modifier::Subsecond,
) -> Option<ParsedItem<'_, u32>> {
use modifier::SubsecondDigits::*;
Some(match modifiers.digits {
One => exactly_n_digits::<1, u32>(input)?.map(|v| v * 100_000_000),
Two => exactly_n_digits::<2, u32>(input)?.map(|v| v * 10_000_000),
Three => exactly_n_digits::<3, u32>(input)?.map(|v| v * 1_000_000),
Four => exactly_n_digits::<4, u32>(input)?.map(|v| v * 100_000),
Five => exactly_n_digits::<5, u32>(input)?.map(|v| v * 10_000),
Six => exactly_n_digits::<6, u32>(input)?.map(|v| v * 1_000),
Seven => exactly_n_digits::<7, u32>(input)?.map(|v| v * 100),
Eight => exactly_n_digits::<8, u32>(input)?.map(|v| v * 10),
Nine => exactly_n_digits::<9, _>(input)?,
OneOrMore => {
let ParsedItem(mut input, mut value) =
any_digit(input)?.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)
}
})
}
// endregion time components
// region: offset components
/// Parse the "hour" component of a `UtcOffset`.
///
/// Returns the value and whether the value is negative. This is used for when "-0" is parsed.
pub(crate) fn parse_offset_hour(
input: &[u8],
modifiers: modifier::OffsetHour,
) -> Option<ParsedItem<'_, (i8, bool)>> {
let ParsedItem(input, sign) = opt(sign)(input);
let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
match sign {
Some(b'-') => Some(ParsedItem(input, (-hour.cast_signed(), true))),
None if modifiers.sign_is_mandatory => None,
_ => Some(ParsedItem(input, (hour.cast_signed(), false))),
}
}
/// Parse the "minute" component of a `UtcOffset`.
pub(crate) fn parse_offset_minute(
input: &[u8],
modifiers: modifier::OffsetMinute,
) -> Option<ParsedItem<'_, i8>> {
Some(
exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
.map(|offset_minute| offset_minute.cast_signed()),
)
}
/// Parse the "second" component of a `UtcOffset`.
pub(crate) fn parse_offset_second(
input: &[u8],
modifiers: modifier::OffsetSecond,
) -> Option<ParsedItem<'_, i8>> {
Some(
exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
.map(|offset_second| offset_second.cast_signed()),
)
}
// endregion offset components
/// Ignore the given number of bytes.
pub(crate) fn parse_ignore(
input: &[u8],
modifiers: modifier::Ignore,
) -> Option<ParsedItem<'_, ()>> {
let modifier::Ignore { count } = modifiers;
let input = input.get((count.get().extend())..)?;
Some(ParsedItem(input, ()))
}
/// Parse the Unix timestamp component.
pub(crate) fn parse_unix_timestamp(
input: &[u8],
modifiers: modifier::UnixTimestamp,
) -> Option<ParsedItem<'_, i128>> {
let ParsedItem(input, sign) = opt(sign)(input);
let ParsedItem(input, nano_timestamp) = match modifiers.precision {
modifier::UnixTimestampPrecision::Second => n_to_m_digits::<1, 14, u128>(input)?
.map(|val| val * Nanosecond::per(Second).extend::<u128>()),
modifier::UnixTimestampPrecision::Millisecond => n_to_m_digits::<1, 17, u128>(input)?
.map(|val| val * Nanosecond::per(Millisecond).extend::<u128>()),
modifier::UnixTimestampPrecision::Microsecond => n_to_m_digits::<1, 20, u128>(input)?
.map(|val| val * Nanosecond::per(Microsecond).extend::<u128>()),
modifier::UnixTimestampPrecision::Nanosecond => n_to_m_digits::<1, 23, _>(input)?,
};
match sign {
Some(b'-') => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
None if modifiers.sign_is_mandatory => None,
_ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
}
}
/// Parse the `end` component, which represents the end of input. If any input is remaining, `None`
/// is returned.
pub(crate) const fn parse_end(input: &[u8], end: modifier::End) -> Option<ParsedItem<'_, ()>> {
let modifier::End {} = end;
if input.is_empty() {
Some(ParsedItem(input, ()))
} else {
None
}
}