use core::mem::MaybeUninit;
use core::num::{NonZeroU16, NonZeroU8};
use crate::error::TryFromParsed::InsufficientInformation;
use crate::format_description::modifier::{WeekNumberRepr, YearRepr};
#[cfg(feature = "alloc")]
use crate::format_description::OwnedFormatItem;
use crate::format_description::{Component, FormatItem};
use crate::parsing::component::{
parse_day, parse_hour, parse_minute, parse_month, parse_offset_hour, parse_offset_minute,
parse_offset_second, parse_ordinal, parse_period, parse_second, parse_subsecond,
parse_week_number, parse_weekday, parse_year, Period,
};
use crate::parsing::ParsedItem;
use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
mod sealed {
use super::*;
pub trait AnyFormatItem {
fn parse_item<'a>(
&self,
parsed: &mut Parsed,
input: &'a [u8],
) -> Result<&'a [u8], error::ParseFromDescription>;
}
}
impl sealed::AnyFormatItem for FormatItem<'_> {
fn parse_item<'a>(
&self,
parsed: &mut Parsed,
input: &'a [u8],
) -> Result<&'a [u8], error::ParseFromDescription> {
match self {
Self::Literal(literal) => Parsed::parse_literal(input, literal),
Self::Component(component) => parsed.parse_component(input, *component),
Self::Compound(compound) => parsed.parse_items(input, compound),
Self::Optional(item) => parsed.parse_item(input, *item).or(Ok(input)),
Self::First(items) => {
let mut first_err = None;
for item in items.iter() {
match parsed.parse_item(input, item) {
Ok(remaining_input) => return Ok(remaining_input),
Err(err) if first_err.is_none() => first_err = Some(err),
Err(_) => {}
}
}
match first_err {
Some(err) => Err(err),
None => Ok(input),
}
}
}
}
}
#[cfg(feature = "alloc")]
impl sealed::AnyFormatItem for OwnedFormatItem {
fn parse_item<'a>(
&self,
parsed: &mut Parsed,
input: &'a [u8],
) -> Result<&'a [u8], error::ParseFromDescription> {
match self {
Self::Literal(literal) => Parsed::parse_literal(input, literal),
Self::Component(component) => parsed.parse_component(input, *component),
Self::Compound(compound) => parsed.parse_items(input, compound),
Self::Optional(item) => parsed.parse_item(input, item.as_ref()).or(Ok(input)),
Self::First(items) => {
let mut first_err = None;
for item in items.iter() {
match parsed.parse_item(input, item) {
Ok(remaining_input) => return Ok(remaining_input),
Err(err) if first_err.is_none() => first_err = Some(err),
Err(_) => {}
}
}
match first_err {
Some(err) => Err(err),
None => Ok(input),
}
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Parsed {
flags: u16,
year: MaybeUninit<i32>,
year_last_two: MaybeUninit<u8>,
iso_year: MaybeUninit<i32>,
iso_year_last_two: MaybeUninit<u8>,
month: Option<Month>,
sunday_week_number: MaybeUninit<u8>,
monday_week_number: MaybeUninit<u8>,
iso_week_number: Option<NonZeroU8>,
weekday: Option<Weekday>,
ordinal: Option<NonZeroU16>,
day: Option<NonZeroU8>,
hour_24: MaybeUninit<u8>,
hour_12: Option<NonZeroU8>,
hour_12_is_pm: Option<bool>,
minute: MaybeUninit<u8>,
second: MaybeUninit<u8>,
subsecond: MaybeUninit<u32>,
offset_hour: MaybeUninit<i8>,
offset_minute: MaybeUninit<i8>,
offset_second: MaybeUninit<i8>,
}
#[allow(clippy::missing_docs_in_private_items)]
impl Parsed {
const YEAR_FLAG: u16 = 1 << 0;
const YEAR_LAST_TWO_FLAG: u16 = 1 << 1;
const ISO_YEAR_FLAG: u16 = 1 << 2;
const ISO_YEAR_LAST_TWO_FLAG: u16 = 1 << 3;
const SUNDAY_WEEK_NUMBER_FLAG: u16 = 1 << 4;
const MONDAY_WEEK_NUMBER_FLAG: u16 = 1 << 5;
const HOUR_24_FLAG: u16 = 1 << 6;
const MINUTE_FLAG: u16 = 1 << 7;
const SECOND_FLAG: u16 = 1 << 8;
const SUBSECOND_FLAG: u16 = 1 << 9;
const OFFSET_HOUR_FLAG: u16 = 1 << 10;
const OFFSET_MINUTE_FLAG: u16 = 1 << 11;
const OFFSET_SECOND_FLAG: u16 = 1 << 12;
const LEAP_SECOND_ALLOWED_FLAG: u16 = 1 << 13;
}
impl Parsed {
pub const fn new() -> Self {
Self {
flags: 0,
year: MaybeUninit::uninit(),
year_last_two: MaybeUninit::uninit(),
iso_year: MaybeUninit::uninit(),
iso_year_last_two: MaybeUninit::uninit(),
month: None,
sunday_week_number: MaybeUninit::uninit(),
monday_week_number: MaybeUninit::uninit(),
iso_week_number: None,
weekday: None,
ordinal: None,
day: None,
hour_24: MaybeUninit::uninit(),
hour_12: None,
hour_12_is_pm: None,
minute: MaybeUninit::uninit(),
second: MaybeUninit::uninit(),
subsecond: MaybeUninit::uninit(),
offset_hour: MaybeUninit::uninit(),
offset_minute: MaybeUninit::uninit(),
offset_second: MaybeUninit::uninit(),
}
}
pub fn parse_item<'a>(
&mut self,
input: &'a [u8],
item: &impl sealed::AnyFormatItem,
) -> Result<&'a [u8], error::ParseFromDescription> {
item.parse_item(self, input)
}
pub fn parse_items<'a>(
&mut self,
mut input: &'a [u8],
items: &[impl sealed::AnyFormatItem],
) -> Result<&'a [u8], error::ParseFromDescription> {
let mut this = *self;
for item in items {
input = this.parse_item(input, item)?;
}
*self = this;
Ok(input)
}
pub fn parse_literal<'a>(
input: &'a [u8],
literal: &[u8],
) -> Result<&'a [u8], error::ParseFromDescription> {
input
.strip_prefix(literal)
.ok_or(error::ParseFromDescription::InvalidLiteral)
}
pub fn parse_component<'a>(
&mut self,
input: &'a [u8],
component: Component,
) -> Result<&'a [u8], error::ParseFromDescription> {
use error::ParseFromDescription::InvalidComponent;
match component {
Component::Day(modifiers) => parse_day(input, modifiers)
.and_then(|parsed| parsed.consume_value(|value| self.set_day(value)))
.ok_or(InvalidComponent("day")),
Component::Month(modifiers) => parse_month(input, modifiers)
.and_then(|parsed| parsed.consume_value(|value| self.set_month(value)))
.ok_or(InvalidComponent("month")),
Component::Ordinal(modifiers) => parse_ordinal(input, modifiers)
.and_then(|parsed| parsed.consume_value(|value| self.set_ordinal(value)))
.ok_or(InvalidComponent("ordinal")),
Component::Weekday(modifiers) => parse_weekday(input, modifiers)
.and_then(|parsed| parsed.consume_value(|value| self.set_weekday(value)))
.ok_or(InvalidComponent("weekday")),
Component::WeekNumber(modifiers) => {
let ParsedItem(remaining, value) =
parse_week_number(input, modifiers).ok_or(InvalidComponent("week number"))?;
match modifiers.repr {
WeekNumberRepr::Iso => {
NonZeroU8::new(value).and_then(|value| self.set_iso_week_number(value))
}
WeekNumberRepr::Sunday => self.set_sunday_week_number(value),
WeekNumberRepr::Monday => self.set_monday_week_number(value),
}
.ok_or(InvalidComponent("week number"))?;
Ok(remaining)
}
Component::Year(modifiers) => {
let ParsedItem(remaining, value) =
parse_year(input, modifiers).ok_or(InvalidComponent("year"))?;
match (modifiers.iso_week_based, modifiers.repr) {
(false, YearRepr::Full) => self.set_year(value),
(false, YearRepr::LastTwo) => self.set_year_last_two(value as _),
(true, YearRepr::Full) => self.set_iso_year(value),
(true, YearRepr::LastTwo) => self.set_iso_year_last_two(value as _),
}
.ok_or(InvalidComponent("year"))?;
Ok(remaining)
}
Component::Hour(modifiers) => {
let ParsedItem(remaining, value) =
parse_hour(input, modifiers).ok_or(InvalidComponent("hour"))?;
if modifiers.is_12_hour_clock {
NonZeroU8::new(value).and_then(|value| self.set_hour_12(value))
} else {
self.set_hour_24(value)
}
.ok_or(InvalidComponent("hour"))?;
Ok(remaining)
}
Component::Minute(modifiers) => parse_minute(input, modifiers)
.and_then(|parsed| parsed.consume_value(|value| self.set_minute(value)))
.ok_or(InvalidComponent("minute")),
Component::Period(modifiers) => parse_period(input, modifiers)
.and_then(|parsed| {
parsed.consume_value(|value| self.set_hour_12_is_pm(value == Period::Pm))
})
.ok_or(InvalidComponent("period")),
Component::Second(modifiers) => parse_second(input, modifiers)
.and_then(|parsed| parsed.consume_value(|value| self.set_second(value)))
.ok_or(InvalidComponent("second")),
Component::Subsecond(modifiers) => parse_subsecond(input, modifiers)
.and_then(|parsed| parsed.consume_value(|value| self.set_subsecond(value)))
.ok_or(InvalidComponent("subsecond")),
Component::OffsetHour(modifiers) => parse_offset_hour(input, modifiers)
.and_then(|parsed| parsed.consume_value(|value| self.set_offset_hour(value)))
.ok_or(InvalidComponent("offset hour")),
Component::OffsetMinute(modifiers) => parse_offset_minute(input, modifiers)
.and_then(|parsed| {
parsed.consume_value(|value| self.set_offset_minute_signed(value))
})
.ok_or(InvalidComponent("offset minute")),
Component::OffsetSecond(modifiers) => parse_offset_second(input, modifiers)
.and_then(|parsed| {
parsed.consume_value(|value| self.set_offset_second_signed(value))
})
.ok_or(InvalidComponent("offset second")),
}
}
}
macro_rules! getters {
($($(@$flag:ident)? $name:ident: $ty:ty),+ $(,)?) => {$(
getters!(! $(@$flag)? $name: $ty);
)*};
(! $name:ident : $ty:ty) => {
pub const fn $name(&self) -> Option<$ty> {
self.$name
}
};
(! @$flag:ident $name:ident : $ty:ty) => {
pub const fn $name(&self) -> Option<$ty> {
if self.flags & Self::$flag != Self::$flag {
None
} else {
Some(unsafe { self.$name.assume_init() })
}
}
};
}
impl Parsed {
getters! {
@YEAR_FLAG year: i32,
@YEAR_LAST_TWO_FLAG year_last_two: u8,
@ISO_YEAR_FLAG iso_year: i32,
@ISO_YEAR_LAST_TWO_FLAG iso_year_last_two: u8,
month: Month,
@SUNDAY_WEEK_NUMBER_FLAG sunday_week_number: u8,
@MONDAY_WEEK_NUMBER_FLAG monday_week_number: u8,
iso_week_number: NonZeroU8,
weekday: Weekday,
ordinal: NonZeroU16,
day: NonZeroU8,
@HOUR_24_FLAG hour_24: u8,
hour_12: NonZeroU8,
hour_12_is_pm: bool,
@MINUTE_FLAG minute: u8,
@SECOND_FLAG second: u8,
@SUBSECOND_FLAG subsecond: u32,
@OFFSET_HOUR_FLAG offset_hour: i8,
}
#[deprecated(since = "0.3.8", note = "use `parsed.offset_minute_signed()` instead")]
pub const fn offset_minute(&self) -> Option<u8> {
Some(const_try_opt!(self.offset_minute_signed()).unsigned_abs())
}
pub const fn offset_minute_signed(&self) -> Option<i8> {
if self.flags & Self::OFFSET_MINUTE_FLAG != Self::OFFSET_MINUTE_FLAG {
None
} else {
Some(unsafe { self.offset_minute.assume_init() })
}
}
#[deprecated(since = "0.3.8", note = "use `parsed.offset_second_signed()` instead")]
pub const fn offset_second(&self) -> Option<u8> {
Some(const_try_opt!(self.offset_second_signed()).unsigned_abs())
}
pub const fn offset_second_signed(&self) -> Option<i8> {
if self.flags & Self::OFFSET_SECOND_FLAG != Self::OFFSET_SECOND_FLAG {
None
} else {
Some(unsafe { self.offset_second.assume_init() })
}
}
pub(crate) const fn leap_second_allowed(&self) -> bool {
self.flags & Self::LEAP_SECOND_ALLOWED_FLAG == Self::LEAP_SECOND_ALLOWED_FLAG
}
}
macro_rules! setters {
($($(@$flag:ident)? $setter_name:ident $name:ident: $ty:ty),+ $(,)?) => {$(
setters!(! $(@$flag)? $setter_name $name: $ty);
)*};
(! $setter_name:ident $name:ident : $ty:ty) => {
pub fn $setter_name(&mut self, value: $ty) -> Option<()> {
self.$name = Some(value);
Some(())
}
};
(! @$flag:ident $setter_name:ident $name:ident : $ty:ty) => {
pub fn $setter_name(&mut self, value: $ty) -> Option<()> {
self.$name = MaybeUninit::new(value);
self.flags |= Self::$flag;
Some(())
}
};
}
impl Parsed {
setters! {
@YEAR_FLAG set_year year: i32,
@YEAR_LAST_TWO_FLAG set_year_last_two year_last_two: u8,
@ISO_YEAR_FLAG set_iso_year iso_year: i32,
@ISO_YEAR_LAST_TWO_FLAG set_iso_year_last_two iso_year_last_two: u8,
set_month month: Month,
@SUNDAY_WEEK_NUMBER_FLAG set_sunday_week_number sunday_week_number: u8,
@MONDAY_WEEK_NUMBER_FLAG set_monday_week_number monday_week_number: u8,
set_iso_week_number iso_week_number: NonZeroU8,
set_weekday weekday: Weekday,
set_ordinal ordinal: NonZeroU16,
set_day day: NonZeroU8,
@HOUR_24_FLAG set_hour_24 hour_24: u8,
set_hour_12 hour_12: NonZeroU8,
set_hour_12_is_pm hour_12_is_pm: bool,
@MINUTE_FLAG set_minute minute: u8,
@SECOND_FLAG set_second second: u8,
@SUBSECOND_FLAG set_subsecond subsecond: u32,
@OFFSET_HOUR_FLAG set_offset_hour offset_hour: i8,
}
#[deprecated(
since = "0.3.8",
note = "use `parsed.set_offset_minute_signed()` instead"
)]
pub fn set_offset_minute(&mut self, value: u8) -> Option<()> {
if value > i8::MAX as u8 {
None
} else {
self.set_offset_minute_signed(value as _)
}
}
pub fn set_offset_minute_signed(&mut self, value: i8) -> Option<()> {
self.offset_minute = MaybeUninit::new(value);
self.flags |= Self::OFFSET_MINUTE_FLAG;
Some(())
}
#[deprecated(
since = "0.3.8",
note = "use `parsed.set_offset_second_signed()` instead"
)]
pub fn set_offset_second(&mut self, value: u8) -> Option<()> {
if value > i8::MAX as u8 {
None
} else {
self.set_offset_second_signed(value as _)
}
}
pub fn set_offset_second_signed(&mut self, value: i8) -> Option<()> {
self.offset_second = MaybeUninit::new(value);
self.flags |= Self::OFFSET_SECOND_FLAG;
Some(())
}
pub(crate) fn set_leap_second_allowed(&mut self, value: bool) {
if value {
self.flags |= Self::LEAP_SECOND_ALLOWED_FLAG;
} else {
self.flags &= !Self::LEAP_SECOND_ALLOWED_FLAG;
}
}
}
macro_rules! builders {
($($(@$flag:ident)? $builder_name:ident $name:ident: $ty:ty),+ $(,)?) => {$(
builders!(! $(@$flag)? $builder_name $name: $ty);
)*};
(! $builder_name:ident $name:ident : $ty:ty) => {
pub const fn $builder_name(mut self, value: $ty) -> Option<Self> {
self.$name = Some(value);
Some(self)
}
};
(! @$flag:ident $builder_name:ident $name:ident : $ty:ty) => {
pub const fn $builder_name(mut self, value: $ty) -> Option<Self> {
self.$name = MaybeUninit::new(value);
self.flags |= Self::$flag;
Some(self)
}
};
}
impl Parsed {
builders! {
@YEAR_FLAG with_year year: i32,
@YEAR_LAST_TWO_FLAG with_year_last_two year_last_two: u8,
@ISO_YEAR_FLAG with_iso_year iso_year: i32,
@ISO_YEAR_LAST_TWO_FLAG with_iso_year_last_two iso_year_last_two: u8,
with_month month: Month,
@SUNDAY_WEEK_NUMBER_FLAG with_sunday_week_number sunday_week_number: u8,
@MONDAY_WEEK_NUMBER_FLAG with_monday_week_number monday_week_number: u8,
with_iso_week_number iso_week_number: NonZeroU8,
with_weekday weekday: Weekday,
with_ordinal ordinal: NonZeroU16,
with_day day: NonZeroU8,
@HOUR_24_FLAG with_hour_24 hour_24: u8,
with_hour_12 hour_12: NonZeroU8,
with_hour_12_is_pm hour_12_is_pm: bool,
@MINUTE_FLAG with_minute minute: u8,
@SECOND_FLAG with_second second: u8,
@SUBSECOND_FLAG with_subsecond subsecond: u32,
@OFFSET_HOUR_FLAG with_offset_hour offset_hour: i8,
}
#[deprecated(
since = "0.3.8",
note = "use `parsed.with_offset_minute_signed()` instead"
)]
pub const fn with_offset_minute(self, value: u8) -> Option<Self> {
if value > i8::MAX as u8 {
None
} else {
self.with_offset_minute_signed(value as _)
}
}
pub const fn with_offset_minute_signed(mut self, value: i8) -> Option<Self> {
self.offset_minute = MaybeUninit::new(value);
self.flags |= Self::OFFSET_MINUTE_FLAG;
Some(self)
}
#[deprecated(
since = "0.3.8",
note = "use `parsed.with_offset_second_signed()` instead"
)]
pub const fn with_offset_second(self, value: u8) -> Option<Self> {
if value > i8::MAX as u8 {
None
} else {
self.with_offset_second_signed(value as _)
}
}
pub const fn with_offset_second_signed(mut self, value: i8) -> Option<Self> {
self.offset_second = MaybeUninit::new(value);
self.flags |= Self::OFFSET_SECOND_FLAG;
Some(self)
}
}
impl TryFrom<Parsed> for Date {
type Error = error::TryFromParsed;
fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
macro_rules! match_ {
(_ => $catch_all:expr $(,)?) => {
$catch_all
};
(($($name:ident),* $(,)?) => $arm:expr, $($rest:tt)*) => {
if let ($(Some($name)),*) = ($(parsed.$name()),*) {
$arm
} else {
match_!($($rest)*)
}
};
}
const fn adjustment(year: i32) -> i16 {
match Date::__from_ordinal_date_unchecked(year, 1).weekday() {
Weekday::Monday => 7,
Weekday::Tuesday => 1,
Weekday::Wednesday => 2,
Weekday::Thursday => 3,
Weekday::Friday => 4,
Weekday::Saturday => 5,
Weekday::Sunday => 6,
}
}
match_! {
(year, ordinal) => Ok(Self::from_ordinal_date(year, ordinal.get())?),
(year, month, day) => Ok(Self::from_calendar_date(year, month, day.get())?),
(iso_year, iso_week_number, weekday) => Ok(Self::from_iso_week_date(
iso_year,
iso_week_number.get(),
weekday,
)?),
(year, sunday_week_number, weekday) => Ok(Self::from_ordinal_date(
year,
(sunday_week_number as i16 * 7 + weekday.number_days_from_sunday() as i16
- adjustment(year)
+ 1) as u16,
)?),
(year, monday_week_number, weekday) => Ok(Self::from_ordinal_date(
year,
(monday_week_number as i16 * 7 + weekday.number_days_from_monday() as i16
- adjustment(year)
+ 1) as u16,
)?),
_ => Err(InsufficientInformation),
}
}
}
impl TryFrom<Parsed> for Time {
type Error = error::TryFromParsed;
fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
let hour = match (parsed.hour_24(), parsed.hour_12(), parsed.hour_12_is_pm()) {
(Some(hour), _, _) => hour,
(_, Some(hour), Some(false)) if hour.get() == 12 => 0,
(_, Some(hour), Some(true)) if hour.get() == 12 => 12,
(_, Some(hour), Some(false)) => hour.get(),
(_, Some(hour), Some(true)) => hour.get() + 12,
_ => return Err(InsufficientInformation),
};
if parsed.hour_24().is_none()
&& parsed.hour_12().is_some()
&& parsed.hour_12_is_pm().is_some()
&& parsed.minute().is_none()
&& parsed.second().is_none()
&& parsed.subsecond().is_none()
{
return Ok(Self::from_hms_nano(hour, 0, 0, 0)?);
}
let minute = parsed.minute().ok_or(InsufficientInformation)?;
let second = parsed.second().unwrap_or(0);
let subsecond = parsed.subsecond().unwrap_or(0);
Ok(Self::from_hms_nano(hour, minute, second, subsecond)?)
}
}
impl TryFrom<Parsed> for UtcOffset {
type Error = error::TryFromParsed;
fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
let hour = parsed.offset_hour().ok_or(InsufficientInformation)?;
let minute = parsed.offset_minute_signed().unwrap_or(0);
let second = parsed.offset_second_signed().unwrap_or(0);
Self::from_hms(hour, minute, second).map_err(|mut err| {
if err.name == "hours" {
err.name = "offset hour";
} else if err.name == "minutes" {
err.name = "offset minute";
} else if err.name == "seconds" {
err.name = "offset second";
}
err.into()
})
}
}
impl TryFrom<Parsed> for PrimitiveDateTime {
type Error = error::TryFromParsed;
fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
Ok(Self::new(parsed.try_into()?, parsed.try_into()?))
}
}
impl TryFrom<Parsed> for OffsetDateTime {
type Error = error::TryFromParsed;
#[allow(clippy::unwrap_in_result)] fn try_from(mut parsed: Parsed) -> Result<Self, Self::Error> {
let leap_second_input = if parsed.leap_second_allowed() && parsed.second() == Some(60) {
parsed.set_second(59).expect("59 is a valid second");
parsed
.set_subsecond(999_999_999)
.expect("999_999_999 is a valid subsecond");
true
} else {
false
};
let dt = PrimitiveDateTime::try_from(parsed)?.assume_offset(parsed.try_into()?);
if leap_second_input && !dt.is_valid_leap_second_stand_in() {
return Err(error::TryFromParsed::ComponentRange(
error::ComponentRange {
name: "second",
minimum: 0,
maximum: 59,
value: 60,
conditional_range: true,
},
));
}
Ok(dt)
}
}