use crate::{
error,
format::{parse_fmt_string, well_known, FormatItem, Padding, Specifier},
Format, UtcOffset, Weekday,
};
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
use core::{
fmt::{self, Display, Formatter},
num::{NonZeroU16, NonZeroU8},
str::FromStr,
};
pub(crate) type ParseResult<T> = Result<T, Error>;
#[cfg_attr(__time_02_supports_non_exhaustive, non_exhaustive)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Error {
InvalidNanosecond,
InvalidSecond,
InvalidMinute,
InvalidHour,
InvalidAmPm,
InvalidMonth,
InvalidYear,
InvalidWeek,
InvalidDayOfWeek,
InvalidDayOfMonth,
InvalidDayOfYear,
InvalidOffset,
MissingFormatSpecifier,
InvalidFormatSpecifier(char),
UnexpectedCharacter {
expected: char,
actual: char,
},
UnexpectedEndOfString,
InsufficientInformation,
ComponentOutOfRange(Box<error::ComponentRange>),
#[cfg(not(__time_02_supports_non_exhaustive))]
#[doc(hidden)]
__NonExhaustive,
}
impl From<error::ComponentRange> for Error {
fn from(error: error::ComponentRange) -> Self {
Error::ComponentOutOfRange(Box::new(error))
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use Error::*;
match self {
InvalidNanosecond => f.write_str("invalid nanosecond"),
InvalidSecond => f.write_str("invalid second"),
InvalidMinute => f.write_str("invalid minute"),
InvalidHour => f.write_str("invalid hour"),
InvalidAmPm => f.write_str("invalid am/pm"),
InvalidMonth => f.write_str("invalid month"),
InvalidYear => f.write_str("invalid year"),
InvalidWeek => f.write_str("invalid week"),
InvalidDayOfWeek => f.write_str("invalid day of week"),
InvalidDayOfMonth => f.write_str("invalid day of month"),
InvalidDayOfYear => f.write_str("invalid day of year"),
InvalidOffset => f.write_str("invalid offset"),
MissingFormatSpecifier => f.write_str("missing format specifier after `%`"),
InvalidFormatSpecifier(c) => write!(f, "invalid format specifier `{}` after `%`", c),
UnexpectedCharacter { expected, actual } => {
write!(f, "expected character `{}`, found `{}`", expected, actual)
}
UnexpectedEndOfString => f.write_str("unexpected end of string"),
InsufficientInformation => {
f.write_str("insufficient information provided to create the requested type")
}
ComponentOutOfRange(e) => write!(f, "{}", e),
#[cfg(not(__time_02_supports_non_exhaustive))]
__NonExhaustive => unreachable!(),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::ComponentOutOfRange(e) => Some(e.as_ref()),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum AmPm {
AM,
PM,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ParsedItems {
pub(crate) week_based_year: Option<i32>,
pub(crate) year: Option<i32>,
pub(crate) month: Option<NonZeroU8>,
pub(crate) day: Option<NonZeroU8>,
pub(crate) weekday: Option<Weekday>,
pub(crate) ordinal_day: Option<NonZeroU16>,
pub(crate) iso_week: Option<NonZeroU8>,
pub(crate) sunday_week: Option<u8>,
pub(crate) monday_week: Option<u8>,
pub(crate) hour_12: Option<NonZeroU8>,
pub(crate) hour_24: Option<u8>,
pub(crate) minute: Option<u8>,
pub(crate) second: Option<u8>,
pub(crate) nanosecond: Option<u32>,
pub(crate) offset: Option<UtcOffset>,
pub(crate) am_pm: Option<AmPm>,
}
impl ParsedItems {
pub(crate) const fn new() -> Self {
Self {
week_based_year: None,
year: None,
month: None,
day: None,
weekday: None,
ordinal_day: None,
iso_week: None,
sunday_week: None,
monday_week: None,
hour_12: None,
hour_24: None,
minute: None,
second: None,
nanosecond: None,
offset: None,
am_pm: None,
}
}
}
pub(crate) fn try_consume_char(s: &mut &str, expected: char) -> ParseResult<()> {
match s.char_indices().next() {
Some((index, actual_char)) if actual_char == expected => {
*s = &s[(index + actual_char.len_utf8())..];
Ok(())
}
Some((_, actual)) => Err(Error::UnexpectedCharacter { expected, actual }),
None => Err(Error::UnexpectedEndOfString),
}
}
pub(crate) fn try_consume_char_case_insensitive(s: &mut &str, expected: char) -> ParseResult<()> {
match s.char_indices().next() {
Some((index, actual_char)) if actual_char.eq_ignore_ascii_case(&expected) => {
*s = &s[(index + actual_char.len_utf8())..];
Ok(())
}
Some((_, actual)) => Err(Error::UnexpectedCharacter { expected, actual }),
None => Err(Error::UnexpectedEndOfString),
}
}
pub(crate) fn try_consume_str(s: &mut &str, expected: &str) -> ParseResult<()> {
if s.starts_with(expected) {
*s = &s[expected.len()..];
Ok(())
} else {
for c in expected.chars() {
try_consume_char(s, c)?;
}
unreachable!("The previous loop should always cause the function to return.");
}
}
pub(crate) fn try_consume_first_match<T: Copy>(
s: &mut &str,
opts: impl IntoIterator<Item = (impl AsRef<str>, T)>,
) -> Option<T> {
opts.into_iter().find_map(|(expected, value)| {
if s.starts_with(expected.as_ref()) {
*s = &s[expected.as_ref().len()..];
Some(value)
} else {
None
}
})
}
pub(crate) fn try_consume_digits<T: FromStr>(
s: &mut &str,
min_digits: usize,
max_digits: usize,
) -> Option<T> {
let len = s
.chars()
.take(max_digits)
.take_while(char::is_ascii_digit)
.count();
if len < min_digits {
return None;
}
let digits = &s[..len];
*s = &s[len..];
digits.parse::<T>().ok()
}
pub(crate) fn try_consume_exact_digits<T: FromStr>(
s: &mut &str,
num_digits: usize,
padding: Padding,
) -> Option<T> {
let pad_size = match padding {
Padding::Space => consume_padding(s, padding, num_digits - 1),
_ => 0,
};
if padding == Padding::None {
try_consume_digits(s, 1, num_digits - pad_size)
} else {
if !s
.chars()
.take(num_digits - pad_size)
.all(|c| c.is_ascii_digit())
{
return None;
}
if (num_digits - pad_size) > s.len() {
return None;
}
let digits = &s[..(num_digits - pad_size)];
*s = &s[(num_digits - pad_size)..];
digits.parse::<T>().ok()
}
}
pub(crate) fn consume_padding(s: &mut &str, padding: Padding, max_chars: usize) -> usize {
let pad_char = match padding {
Padding::Space => ' ',
Padding::Zero => '0',
Padding::None => return 0,
};
let pad_width = s
.chars()
.take(max_chars)
.take_while(|&c| c == pad_char)
.count();
*s = &s[pad_width..];
pad_width
}
#[allow(clippy::too_many_lines)]
pub(crate) fn parse(s: &str, format: &Format) -> ParseResult<ParsedItems> {
use super::{date, offset, time};
let mut s = <&str>::clone(&s);
let mut items = ParsedItems::new();
macro_rules! parse {
($module:ident :: $specifier_fn:ident $( ( $($params:expr),* ) )?) => {
$module::$specifier_fn(&mut items, &mut s, $( $($params),* )?)?
};
}
macro_rules! parse_char {
($c:literal) => {
try_consume_char(&mut s, $c)?
};
}
match &format {
Format::Rfc3339 => well_known::rfc3339::parse(&mut items, &mut s)?,
Format::Custom(format) => {
for item in parse_fmt_string(format) {
match item {
FormatItem::Literal(expected) => try_consume_str(&mut s, expected)?,
FormatItem::Specifier(specifier) => {
use Specifier::*;
match specifier {
a => parse!(date::parse_a),
A => parse!(date::parse_A),
b => parse!(date::parse_b),
B => parse!(date::parse_B),
c => {
parse!(date::parse_a);
parse_char!(' ');
parse!(date::parse_b);
parse_char!(' ');
parse!(date::parse_d(Padding::None));
parse_char!(' ');
parse!(time::parse_H(Padding::None));
parse_char!(':');
parse!(time::parse_M(Padding::Zero));
parse_char!(':');
parse!(time::parse_S(Padding::Zero));
parse_char!(' ');
parse!(date::parse_Y(Padding::None));
}
C { padding } => parse!(date::parse_C(padding)),
d { padding } => parse!(date::parse_d(padding)),
D => {
parse!(date::parse_m(Padding::None));
parse_char!('/');
parse!(date::parse_d(Padding::Zero));
parse_char!('/');
parse!(date::parse_y(Padding::Zero));
}
F => {
parse!(date::parse_Y(Padding::None));
parse_char!('-');
parse!(date::parse_m(Padding::Zero));
parse_char!('-');
parse!(date::parse_d(Padding::Zero));
}
g { padding } => parse!(date::parse_g(padding)),
G { padding } => parse!(date::parse_G(padding)),
H { padding } => parse!(time::parse_H(padding)),
I { padding } => parse!(time::parse_I(padding)),
j { padding } => parse!(date::parse_j(padding)),
M { padding } => parse!(time::parse_M(padding)),
m { padding } => parse!(date::parse_m(padding)),
N => parse!(time::parse_N),
p => parse!(time::parse_p),
P => parse!(time::parse_P),
r => {
parse!(time::parse_I(Padding::None));
parse_char!(':');
parse!(time::parse_M(Padding::Zero));
parse_char!(':');
parse!(time::parse_S(Padding::Zero));
parse_char!(' ');
parse!(time::parse_p);
}
R => {
parse!(time::parse_H(Padding::None));
parse_char!(':');
parse!(time::parse_M(Padding::Zero));
}
S { padding } => parse!(time::parse_S(padding)),
T => {
parse!(time::parse_H(Padding::None));
parse_char!(':');
parse!(time::parse_M(Padding::Zero));
parse_char!(':');
parse!(time::parse_S(Padding::Zero));
}
u => parse!(date::parse_u),
U { padding } => parse!(date::parse_U(padding)),
V { padding } => parse!(date::parse_V(padding)),
w => parse!(date::parse_w),
W { padding } => parse!(date::parse_W(padding)),
y { padding } => parse!(date::parse_y(padding)),
z => parse!(offset::parse_z),
Y { padding } => parse!(date::parse_Y(padding)),
}
}
}
}
}
#[cfg(not(__time_02_supports_non_exhaustive))]
Format::__NonExhaustive => unreachable!(),
}
Ok(items)
}