forked from mirrors/gecko-dev
903 lines
35 KiB
Rust
903 lines
35 KiB
Rust
//! Information parsed from an input and format description.
|
|
|
|
use core::num::{NonZeroU16, NonZeroU8};
|
|
|
|
use deranged::{
|
|
OptionRangedI128, OptionRangedI32, OptionRangedI8, OptionRangedU16, OptionRangedU32,
|
|
OptionRangedU8, RangedI128, RangedI32, RangedI8, RangedU16, RangedU32, RangedU8,
|
|
};
|
|
use num_conv::prelude::*;
|
|
|
|
use crate::convert::{Day, Hour, Minute, Nanosecond, Second};
|
|
use crate::date::{MAX_YEAR, MIN_YEAR};
|
|
use crate::error::TryFromParsed::InsufficientInformation;
|
|
#[cfg(feature = "alloc")]
|
|
use crate::format_description::OwnedFormatItem;
|
|
use crate::format_description::{modifier, BorrowedFormatItem, Component};
|
|
use crate::internal_macros::{bug, const_try_opt};
|
|
use crate::parsing::component::{
|
|
parse_day, parse_end, parse_hour, parse_ignore, parse_minute, parse_month, parse_offset_hour,
|
|
parse_offset_minute, parse_offset_second, parse_ordinal, parse_period, parse_second,
|
|
parse_subsecond, parse_unix_timestamp, parse_week_number, parse_weekday, parse_year, Period,
|
|
};
|
|
use crate::parsing::ParsedItem;
|
|
use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
|
|
|
|
/// Sealed to prevent downstream implementations.
|
|
mod sealed {
|
|
use super::*;
|
|
|
|
/// A trait to allow `parse_item` to be generic.
|
|
pub trait AnyFormatItem {
|
|
/// Parse a single item, returning the remaining input on success.
|
|
fn parse_item<'a>(
|
|
&self,
|
|
parsed: &mut Parsed,
|
|
input: &'a [u8],
|
|
) -> Result<&'a [u8], error::ParseFromDescription>;
|
|
}
|
|
}
|
|
|
|
impl sealed::AnyFormatItem for BorrowedFormatItem<'_> {
|
|
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),
|
|
// This location will be reached if the slice is empty, skipping the `for` loop.
|
|
// As this case is expected to be uncommon, there's no need to check up front.
|
|
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),
|
|
// This location will be reached if the slice is empty, skipping the `for` loop.
|
|
// As this case is expected to be uncommon, there's no need to check up front.
|
|
None => Ok(input),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// All information parsed.
|
|
///
|
|
/// This information is directly used to construct the final values.
|
|
///
|
|
/// Most users will not need think about this struct in any way. It is public to allow for manual
|
|
/// control over values, in the instance that the default parser is insufficient.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct Parsed {
|
|
/// Calendar year.
|
|
year: OptionRangedI32<{ MIN_YEAR }, { MAX_YEAR }>,
|
|
/// The last two digits of the calendar year.
|
|
year_last_two: OptionRangedU8<0, 99>,
|
|
/// Year of the [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date).
|
|
iso_year: OptionRangedI32<{ MIN_YEAR }, { MAX_YEAR }>,
|
|
/// The last two digits of the ISO week year.
|
|
iso_year_last_two: OptionRangedU8<0, 99>,
|
|
/// Month of the year.
|
|
month: Option<Month>,
|
|
/// Week of the year, where week one begins on the first Sunday of the calendar year.
|
|
sunday_week_number: OptionRangedU8<0, 53>,
|
|
/// Week of the year, where week one begins on the first Monday of the calendar year.
|
|
monday_week_number: OptionRangedU8<0, 53>,
|
|
/// Week of the year, where week one is the Monday-to-Sunday period containing January 4.
|
|
iso_week_number: OptionRangedU8<1, 53>,
|
|
/// Day of the week.
|
|
weekday: Option<Weekday>,
|
|
/// Day of the year.
|
|
ordinal: OptionRangedU16<1, 366>,
|
|
/// Day of the month.
|
|
day: OptionRangedU8<1, 31>,
|
|
/// Hour within the day.
|
|
hour_24: OptionRangedU8<0, { Hour::per(Day) - 1 }>,
|
|
/// Hour within the 12-hour period (midnight to noon or vice versa). This is typically used in
|
|
/// conjunction with AM/PM, which is indicated by the `hour_12_is_pm` field.
|
|
hour_12: OptionRangedU8<1, 12>,
|
|
/// Whether the `hour_12` field indicates a time that "PM".
|
|
hour_12_is_pm: Option<bool>,
|
|
/// Minute within the hour.
|
|
// minute: MaybeUninit<u8>,
|
|
minute: OptionRangedU8<0, { Minute::per(Hour) - 1 }>,
|
|
/// Second within the minute.
|
|
// do not subtract one, as leap seconds may be allowed
|
|
second: OptionRangedU8<0, { Second::per(Minute) }>,
|
|
/// Nanosecond within the second.
|
|
subsecond: OptionRangedU32<0, { Nanosecond::per(Second) - 1 }>,
|
|
/// Whole hours of the UTC offset.
|
|
offset_hour: OptionRangedI8<-23, 23>,
|
|
/// Minutes within the hour of the UTC offset.
|
|
offset_minute:
|
|
OptionRangedI8<{ -((Minute::per(Hour) - 1) as i8) }, { (Minute::per(Hour) - 1) as _ }>,
|
|
/// Seconds within the minute of the UTC offset.
|
|
offset_second:
|
|
OptionRangedI8<{ -((Second::per(Minute) - 1) as i8) }, { (Second::per(Minute) - 1) as _ }>,
|
|
/// The Unix timestamp in nanoseconds.
|
|
unix_timestamp_nanos: OptionRangedI128<
|
|
{
|
|
OffsetDateTime::new_in_offset(Date::MIN, Time::MIDNIGHT, UtcOffset::UTC)
|
|
.unix_timestamp_nanos()
|
|
},
|
|
{
|
|
OffsetDateTime::new_in_offset(Date::MAX, Time::MAX, UtcOffset::UTC)
|
|
.unix_timestamp_nanos()
|
|
},
|
|
>,
|
|
/// Indicates whether the [`UtcOffset`] is negative. This information is obtained when parsing
|
|
/// the offset hour, but may not otherwise be stored due to "-0" being equivalent to "0".
|
|
offset_is_negative: Option<bool>,
|
|
/// Indicates whether a leap second is permitted to be parsed. This is required by some
|
|
/// well-known formats.
|
|
pub(super) leap_second_allowed: bool,
|
|
}
|
|
|
|
impl Default for Parsed {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl Parsed {
|
|
/// Create a new instance of `Parsed` with no information known.
|
|
pub const fn new() -> Self {
|
|
Self {
|
|
year: OptionRangedI32::None,
|
|
year_last_two: OptionRangedU8::None,
|
|
iso_year: OptionRangedI32::None,
|
|
iso_year_last_two: OptionRangedU8::None,
|
|
month: None,
|
|
sunday_week_number: OptionRangedU8::None,
|
|
monday_week_number: OptionRangedU8::None,
|
|
iso_week_number: OptionRangedU8::None,
|
|
weekday: None,
|
|
ordinal: OptionRangedU16::None,
|
|
day: OptionRangedU8::None,
|
|
hour_24: OptionRangedU8::None,
|
|
hour_12: OptionRangedU8::None,
|
|
hour_12_is_pm: None,
|
|
minute: OptionRangedU8::None,
|
|
second: OptionRangedU8::None,
|
|
subsecond: OptionRangedU32::None,
|
|
offset_hour: OptionRangedI8::None,
|
|
offset_minute: OptionRangedI8::None,
|
|
offset_second: OptionRangedI8::None,
|
|
unix_timestamp_nanos: OptionRangedI128::None,
|
|
offset_is_negative: None,
|
|
leap_second_allowed: false,
|
|
}
|
|
}
|
|
|
|
/// Parse a single [`BorrowedFormatItem`] or [`OwnedFormatItem`], mutating the struct. The
|
|
/// remaining input is returned as the `Ok` value.
|
|
///
|
|
/// If a [`BorrowedFormatItem::Optional`] or [`OwnedFormatItem::Optional`] is passed, parsing
|
|
/// will not fail; the input will be returned as-is if the expected format is not present.
|
|
pub fn parse_item<'a>(
|
|
&mut self,
|
|
input: &'a [u8],
|
|
item: &impl sealed::AnyFormatItem,
|
|
) -> Result<&'a [u8], error::ParseFromDescription> {
|
|
item.parse_item(self, input)
|
|
}
|
|
|
|
/// Parse a sequence of [`BorrowedFormatItem`]s or [`OwnedFormatItem`]s, mutating the struct.
|
|
/// The remaining input is returned as the `Ok` value.
|
|
///
|
|
/// This method will fail if any of the contained [`BorrowedFormatItem`]s or
|
|
/// [`OwnedFormatItem`]s fail to parse. `self` will not be mutated in this instance.
|
|
pub fn parse_items<'a>(
|
|
&mut self,
|
|
mut input: &'a [u8],
|
|
items: &[impl sealed::AnyFormatItem],
|
|
) -> Result<&'a [u8], error::ParseFromDescription> {
|
|
// Make a copy that we can mutate. It will only be set to the user's copy if everything
|
|
// succeeds.
|
|
let mut this = *self;
|
|
for item in items {
|
|
input = this.parse_item(input, item)?;
|
|
}
|
|
*self = this;
|
|
Ok(input)
|
|
}
|
|
|
|
/// Parse a literal byte sequence. The remaining input is returned as the `Ok` value.
|
|
pub fn parse_literal<'a>(
|
|
input: &'a [u8],
|
|
literal: &[u8],
|
|
) -> Result<&'a [u8], error::ParseFromDescription> {
|
|
input
|
|
.strip_prefix(literal)
|
|
.ok_or(error::ParseFromDescription::InvalidLiteral)
|
|
}
|
|
|
|
/// Parse a single component, mutating the struct. The remaining input is returned as the `Ok`
|
|
/// value.
|
|
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 {
|
|
modifier::WeekNumberRepr::Iso => {
|
|
NonZeroU8::new(value).and_then(|value| self.set_iso_week_number(value))
|
|
}
|
|
modifier::WeekNumberRepr::Sunday => self.set_sunday_week_number(value),
|
|
modifier::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, modifier::YearRepr::Full) => self.set_year(value),
|
|
(false, modifier::YearRepr::LastTwo) => {
|
|
self.set_year_last_two(value.cast_unsigned().truncate())
|
|
}
|
|
(true, modifier::YearRepr::Full) => self.set_iso_year(value),
|
|
(true, modifier::YearRepr::LastTwo) => {
|
|
self.set_iso_year_last_two(value.cast_unsigned().truncate())
|
|
}
|
|
}
|
|
.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, is_negative)| {
|
|
self.set_offset_hour(value)?;
|
|
self.offset_is_negative = Some(is_negative);
|
|
Some(())
|
|
})
|
|
})
|
|
.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")),
|
|
Component::Ignore(modifiers) => parse_ignore(input, modifiers)
|
|
.map(ParsedItem::<()>::into_inner)
|
|
.ok_or(InvalidComponent("ignore")),
|
|
Component::UnixTimestamp(modifiers) => parse_unix_timestamp(input, modifiers)
|
|
.and_then(|parsed| {
|
|
parsed.consume_value(|value| self.set_unix_timestamp_nanos(value))
|
|
})
|
|
.ok_or(InvalidComponent("unix_timestamp")),
|
|
Component::End(modifiers) => parse_end(input, modifiers)
|
|
.map(ParsedItem::<()>::into_inner)
|
|
.ok_or(error::ParseFromDescription::UnexpectedTrailingCharacters),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Getter methods
|
|
impl Parsed {
|
|
/// Obtain the `year` component.
|
|
pub const fn year(&self) -> Option<i32> {
|
|
self.year.get_primitive()
|
|
}
|
|
|
|
/// Obtain the `year_last_two` component.
|
|
pub const fn year_last_two(&self) -> Option<u8> {
|
|
self.year_last_two.get_primitive()
|
|
}
|
|
|
|
/// Obtain the `iso_year` component.
|
|
pub const fn iso_year(&self) -> Option<i32> {
|
|
self.iso_year.get_primitive()
|
|
}
|
|
|
|
/// Obtain the `iso_year_last_two` component.
|
|
pub const fn iso_year_last_two(&self) -> Option<u8> {
|
|
self.iso_year_last_two.get_primitive()
|
|
}
|
|
|
|
/// Obtain the `month` component.
|
|
pub const fn month(&self) -> Option<Month> {
|
|
self.month
|
|
}
|
|
|
|
/// Obtain the `sunday_week_number` component.
|
|
pub const fn sunday_week_number(&self) -> Option<u8> {
|
|
self.sunday_week_number.get_primitive()
|
|
}
|
|
|
|
/// Obtain the `monday_week_number` component.
|
|
pub const fn monday_week_number(&self) -> Option<u8> {
|
|
self.monday_week_number.get_primitive()
|
|
}
|
|
|
|
/// Obtain the `iso_week_number` component.
|
|
pub const fn iso_week_number(&self) -> Option<NonZeroU8> {
|
|
NonZeroU8::new(const_try_opt!(self.iso_week_number.get_primitive()))
|
|
}
|
|
|
|
/// Obtain the `weekday` component.
|
|
pub const fn weekday(&self) -> Option<Weekday> {
|
|
self.weekday
|
|
}
|
|
|
|
/// Obtain the `ordinal` component.
|
|
pub const fn ordinal(&self) -> Option<NonZeroU16> {
|
|
NonZeroU16::new(const_try_opt!(self.ordinal.get_primitive()))
|
|
}
|
|
|
|
/// Obtain the `day` component.
|
|
pub const fn day(&self) -> Option<NonZeroU8> {
|
|
NonZeroU8::new(const_try_opt!(self.day.get_primitive()))
|
|
}
|
|
|
|
/// Obtain the `hour_24` component.
|
|
pub const fn hour_24(&self) -> Option<u8> {
|
|
self.hour_24.get_primitive()
|
|
}
|
|
|
|
/// Obtain the `hour_12` component.
|
|
pub const fn hour_12(&self) -> Option<NonZeroU8> {
|
|
NonZeroU8::new(const_try_opt!(self.hour_12.get_primitive()))
|
|
}
|
|
|
|
/// Obtain the `hour_12_is_pm` component.
|
|
pub const fn hour_12_is_pm(&self) -> Option<bool> {
|
|
self.hour_12_is_pm
|
|
}
|
|
|
|
/// Obtain the `minute` component.
|
|
pub const fn minute(&self) -> Option<u8> {
|
|
self.minute.get_primitive()
|
|
}
|
|
|
|
/// Obtain the `second` component.
|
|
pub const fn second(&self) -> Option<u8> {
|
|
self.second.get_primitive()
|
|
}
|
|
|
|
/// Obtain the `subsecond` component.
|
|
pub const fn subsecond(&self) -> Option<u32> {
|
|
self.subsecond.get_primitive()
|
|
}
|
|
|
|
/// Obtain the `offset_hour` component.
|
|
pub const fn offset_hour(&self) -> Option<i8> {
|
|
self.offset_hour.get_primitive()
|
|
}
|
|
|
|
/// Obtain the absolute value of the `offset_minute` component.
|
|
#[doc(hidden)]
|
|
#[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())
|
|
}
|
|
|
|
/// Obtain the `offset_minute` component.
|
|
pub const fn offset_minute_signed(&self) -> Option<i8> {
|
|
match (self.offset_minute.get_primitive(), self.offset_is_negative) {
|
|
(Some(offset_minute), Some(true)) => Some(-offset_minute),
|
|
(Some(offset_minute), _) => Some(offset_minute),
|
|
(None, _) => None,
|
|
}
|
|
}
|
|
|
|
/// Obtain the absolute value of the `offset_second` component.
|
|
#[doc(hidden)]
|
|
#[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())
|
|
}
|
|
|
|
/// Obtain the `offset_second` component.
|
|
pub const fn offset_second_signed(&self) -> Option<i8> {
|
|
match (self.offset_second.get_primitive(), self.offset_is_negative) {
|
|
(Some(offset_second), Some(true)) => Some(-offset_second),
|
|
(Some(offset_second), _) => Some(offset_second),
|
|
(None, _) => None,
|
|
}
|
|
}
|
|
|
|
/// Obtain the `unix_timestamp_nanos` component.
|
|
pub const fn unix_timestamp_nanos(&self) -> Option<i128> {
|
|
self.unix_timestamp_nanos.get_primitive()
|
|
}
|
|
}
|
|
|
|
/// Generate setters based on the builders.
|
|
macro_rules! setters {
|
|
($($name:ident $setter:ident $builder:ident $type:ty;)*) => {$(
|
|
#[doc = concat!("Set the `", stringify!($setter), "` component.")]
|
|
pub fn $setter(&mut self, value: $type) -> Option<()> {
|
|
*self = self.$builder(value)?;
|
|
Some(())
|
|
}
|
|
)*};
|
|
}
|
|
|
|
/// Setter methods
|
|
///
|
|
/// All setters return `Option<()>`, which is `Some` if the value was set, and `None` if not. The
|
|
/// setters _may_ fail if the value is invalid, though behavior is not guaranteed.
|
|
impl Parsed {
|
|
setters! {
|
|
year set_year with_year i32;
|
|
year_last_two set_year_last_two with_year_last_two u8;
|
|
iso_year set_iso_year with_iso_year i32;
|
|
iso_year_last_two set_iso_year_last_two with_iso_year_last_two u8;
|
|
month set_month with_month Month;
|
|
sunday_week_number set_sunday_week_number with_sunday_week_number u8;
|
|
monday_week_number set_monday_week_number with_monday_week_number u8;
|
|
iso_week_number set_iso_week_number with_iso_week_number NonZeroU8;
|
|
weekday set_weekday with_weekday Weekday;
|
|
ordinal set_ordinal with_ordinal NonZeroU16;
|
|
day set_day with_day NonZeroU8;
|
|
hour_24 set_hour_24 with_hour_24 u8;
|
|
hour_12 set_hour_12 with_hour_12 NonZeroU8;
|
|
hour_12_is_pm set_hour_12_is_pm with_hour_12_is_pm bool;
|
|
minute set_minute with_minute u8;
|
|
second set_second with_second u8;
|
|
subsecond set_subsecond with_subsecond u32;
|
|
offset_hour set_offset_hour with_offset_hour i8;
|
|
offset_minute set_offset_minute_signed with_offset_minute_signed i8;
|
|
offset_second set_offset_second_signed with_offset_second_signed i8;
|
|
unix_timestamp_nanos set_unix_timestamp_nanos with_unix_timestamp_nanos i128;
|
|
}
|
|
|
|
/// Set the `offset_minute` component.
|
|
#[doc(hidden)]
|
|
#[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.cast_unsigned() {
|
|
None
|
|
} else {
|
|
self.set_offset_minute_signed(value.cast_signed())
|
|
}
|
|
}
|
|
|
|
/// Set the `offset_minute` component.
|
|
#[doc(hidden)]
|
|
#[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.cast_unsigned() {
|
|
None
|
|
} else {
|
|
self.set_offset_second_signed(value.cast_signed())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Builder methods
|
|
///
|
|
/// All builder methods return `Option<Self>`, which is `Some` if the value was set, and `None` if
|
|
/// not. The builder methods _may_ fail if the value is invalid, though behavior is not guaranteed.
|
|
impl Parsed {
|
|
/// Set the `year` component and return `self`.
|
|
pub const fn with_year(mut self, value: i32) -> Option<Self> {
|
|
self.year = OptionRangedI32::Some(const_try_opt!(RangedI32::new(value)));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `year_last_two` component and return `self`.
|
|
pub const fn with_year_last_two(mut self, value: u8) -> Option<Self> {
|
|
self.year_last_two = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value)));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `iso_year` component and return `self`.
|
|
pub const fn with_iso_year(mut self, value: i32) -> Option<Self> {
|
|
self.iso_year = OptionRangedI32::Some(const_try_opt!(RangedI32::new(value)));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `iso_year_last_two` component and return `self`.
|
|
pub const fn with_iso_year_last_two(mut self, value: u8) -> Option<Self> {
|
|
self.iso_year_last_two = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value)));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `month` component and return `self`.
|
|
pub const fn with_month(mut self, value: Month) -> Option<Self> {
|
|
self.month = Some(value);
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `sunday_week_number` component and return `self`.
|
|
pub const fn with_sunday_week_number(mut self, value: u8) -> Option<Self> {
|
|
self.sunday_week_number = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value)));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `monday_week_number` component and return `self`.
|
|
pub const fn with_monday_week_number(mut self, value: u8) -> Option<Self> {
|
|
self.monday_week_number = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value)));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `iso_week_number` component and return `self`.
|
|
pub const fn with_iso_week_number(mut self, value: NonZeroU8) -> Option<Self> {
|
|
self.iso_week_number = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value.get())));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `weekday` component and return `self`.
|
|
pub const fn with_weekday(mut self, value: Weekday) -> Option<Self> {
|
|
self.weekday = Some(value);
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `ordinal` component and return `self`.
|
|
pub const fn with_ordinal(mut self, value: NonZeroU16) -> Option<Self> {
|
|
self.ordinal = OptionRangedU16::Some(const_try_opt!(RangedU16::new(value.get())));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `day` component and return `self`.
|
|
pub const fn with_day(mut self, value: NonZeroU8) -> Option<Self> {
|
|
self.day = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value.get())));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `hour_24` component and return `self`.
|
|
pub const fn with_hour_24(mut self, value: u8) -> Option<Self> {
|
|
self.hour_24 = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value)));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `hour_12` component and return `self`.
|
|
pub const fn with_hour_12(mut self, value: NonZeroU8) -> Option<Self> {
|
|
self.hour_12 = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value.get())));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `hour_12_is_pm` component and return `self`.
|
|
pub const fn with_hour_12_is_pm(mut self, value: bool) -> Option<Self> {
|
|
self.hour_12_is_pm = Some(value);
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `minute` component and return `self`.
|
|
pub const fn with_minute(mut self, value: u8) -> Option<Self> {
|
|
self.minute = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value)));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `second` component and return `self`.
|
|
pub const fn with_second(mut self, value: u8) -> Option<Self> {
|
|
self.second = OptionRangedU8::Some(const_try_opt!(RangedU8::new(value)));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `subsecond` component and return `self`.
|
|
pub const fn with_subsecond(mut self, value: u32) -> Option<Self> {
|
|
self.subsecond = OptionRangedU32::Some(const_try_opt!(RangedU32::new(value)));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `offset_hour` component and return `self`.
|
|
pub const fn with_offset_hour(mut self, value: i8) -> Option<Self> {
|
|
self.offset_hour = OptionRangedI8::Some(const_try_opt!(RangedI8::new(value)));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `offset_minute` component and return `self`.
|
|
#[doc(hidden)]
|
|
#[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 _)
|
|
}
|
|
}
|
|
|
|
/// Set the `offset_minute` component and return `self`.
|
|
pub const fn with_offset_minute_signed(mut self, value: i8) -> Option<Self> {
|
|
self.offset_minute = OptionRangedI8::Some(const_try_opt!(RangedI8::new(value)));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `offset_minute` component and return `self`.
|
|
#[doc(hidden)]
|
|
#[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 _)
|
|
}
|
|
}
|
|
|
|
/// Set the `offset_second` component and return `self`.
|
|
pub const fn with_offset_second_signed(mut self, value: i8) -> Option<Self> {
|
|
self.offset_second = OptionRangedI8::Some(const_try_opt!(RangedI8::new(value)));
|
|
Some(self)
|
|
}
|
|
|
|
/// Set the `unix_timestamp_nanos` component and return `self`.
|
|
pub const fn with_unix_timestamp_nanos(mut self, value: i128) -> Option<Self> {
|
|
self.unix_timestamp_nanos = OptionRangedI128::Some(const_try_opt!(RangedI128::new(value)));
|
|
Some(self)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<Parsed> for Date {
|
|
type Error = error::TryFromParsed;
|
|
|
|
fn try_from(parsed: Parsed) -> Result<Self, Self::Error> {
|
|
/// Match on the components that need to be present.
|
|
macro_rules! match_ {
|
|
(_ => $catch_all:expr $(,)?) => {
|
|
$catch_all
|
|
};
|
|
(($($name:ident),* $(,)?) => $arm:expr, $($rest:tt)*) => {
|
|
if let ($(Some($name)),*) = ($(parsed.$name()),*) {
|
|
$arm
|
|
} else {
|
|
match_!($($rest)*)
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Get the value needed to adjust the ordinal day for Sunday and Monday-based week
|
|
/// numbering.
|
|
const fn adjustment(year: i32) -> i16 {
|
|
// Safety: `ordinal` is not zero.
|
|
match unsafe { 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,
|
|
}
|
|
}
|
|
|
|
// TODO Only the basics have been covered. There are many other valid values that are not
|
|
// currently constructed from the information known.
|
|
|
|
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.cast_signed().extend::<i16>() * 7
|
|
+ weekday.number_days_from_sunday().cast_signed().extend::<i16>()
|
|
- adjustment(year)
|
|
+ 1).cast_unsigned(),
|
|
)?),
|
|
(year, monday_week_number, weekday) => Ok(Self::from_ordinal_date(
|
|
year,
|
|
(monday_week_number.cast_signed().extend::<i16>() * 7
|
|
+ weekday.number_days_from_monday().cast_signed().extend::<i16>()
|
|
- adjustment(year)
|
|
+ 1).cast_unsigned(),
|
|
)?),
|
|
_ => 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)?);
|
|
}
|
|
|
|
// Reject combinations such as hour-second with minute omitted.
|
|
match (parsed.minute(), parsed.second(), parsed.subsecond()) {
|
|
(None, None, None) => Ok(Self::from_hms_nano(hour, 0, 0, 0)?),
|
|
(Some(minute), None, None) => Ok(Self::from_hms_nano(hour, minute, 0, 0)?),
|
|
(Some(minute), Some(second), None) => Ok(Self::from_hms_nano(hour, minute, second, 0)?),
|
|
(Some(minute), Some(second), Some(subsecond)) => {
|
|
Ok(Self::from_hms_nano(hour, minute, second, subsecond)?)
|
|
}
|
|
_ => Err(InsufficientInformation),
|
|
}
|
|
}
|
|
}
|
|
|
|
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| {
|
|
// Provide the user a more accurate error.
|
|
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;
|
|
|
|
fn try_from(mut parsed: Parsed) -> Result<Self, Self::Error> {
|
|
if let Some(timestamp) = parsed.unix_timestamp_nanos() {
|
|
let mut value = Self::from_unix_timestamp_nanos(timestamp)?;
|
|
if let Some(subsecond) = parsed.subsecond() {
|
|
value = value.replace_nanosecond(subsecond)?;
|
|
}
|
|
return Ok(value);
|
|
}
|
|
|
|
// Some well-known formats explicitly allow leap seconds. We don't currently support them,
|
|
// so treat it as the nearest preceding moment that can be represented. Because leap seconds
|
|
// always fall at the end of a month UTC, reject any that are at other times.
|
|
let leap_second_input = if parsed.leap_second_allowed && parsed.second() == Some(60) {
|
|
if parsed.set_second(59).is_none() {
|
|
bug!("59 is a valid second");
|
|
}
|
|
if parsed.set_subsecond(999_999_999).is_none() {
|
|
bug!("999_999_999 is a valid subsecond");
|
|
}
|
|
true
|
|
} else {
|
|
false
|
|
};
|
|
|
|
let dt = Self::new_in_offset(
|
|
Date::try_from(parsed)?,
|
|
Time::try_from(parsed)?,
|
|
UtcOffset::try_from(parsed)?,
|
|
);
|
|
|
|
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)
|
|
}
|
|
}
|