diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/rust/time/src/parsing/iso8601.rs | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/time/src/parsing/iso8601.rs')
-rw-r--r-- | third_party/rust/time/src/parsing/iso8601.rs | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/third_party/rust/time/src/parsing/iso8601.rs b/third_party/rust/time/src/parsing/iso8601.rs new file mode 100644 index 0000000000..87e517e376 --- /dev/null +++ b/third_party/rust/time/src/parsing/iso8601.rs @@ -0,0 +1,322 @@ +//! Parse parts of an ISO 8601-formatted value. + +use crate::convert::*; +use crate::error; +use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral}; +use crate::format_description::well_known::iso8601::EncodedConfig; +use crate::format_description::well_known::Iso8601; +use crate::parsing::combinator::rfc::iso8601::{ + day, dayk, dayo, float, hour, min, month, week, year, ExtendedKind, +}; +use crate::parsing::combinator::{ascii_char, sign}; +use crate::parsing::{Parsed, ParsedItem}; + +impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> { + // Basic: [year][month][day] + // Extended: [year]["-"][month]["-"][day] + // Basic: [year][dayo] + // Extended: [year]["-"][dayo] + // Basic: [year]["W"][week][dayk] + // Extended: [year]["-"]["W"][week]["-"][dayk] + /// Parse a date in the basic or extended format. Reduced precision is permitted. + pub(crate) fn parse_date<'a>( + parsed: &'a mut Parsed, + extended_kind: &'a mut ExtendedKind, + ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a { + move |input| { + // Same for any acceptable format. + let ParsedItem(mut input, year) = year(input).ok_or(InvalidComponent("year"))?; + *extended_kind = match ascii_char::<b'-'>(input) { + Some(ParsedItem(new_input, ())) => { + input = new_input; + ExtendedKind::Extended + } + None => ExtendedKind::Basic, // no separator before mandatory month/ordinal/week + }; + + let mut ret_error = match (|| { + let ParsedItem(mut input, month) = month(input).ok_or(InvalidComponent("month"))?; + if extended_kind.is_extended() { + input = ascii_char::<b'-'>(input) + .ok_or(InvalidLiteral)? + .into_inner(); + } + let ParsedItem(input, day) = day(input).ok_or(InvalidComponent("day"))?; + Ok(ParsedItem(input, (month, day))) + })() { + Ok(ParsedItem(input, (month, day))) => { + *parsed = parsed + .with_year(year) + .ok_or(InvalidComponent("year"))? + .with_month(month) + .ok_or(InvalidComponent("month"))? + .with_day(day) + .ok_or(InvalidComponent("day"))?; + return Ok(input); + } + Err(err) => err, + }; + + // Don't check for `None`, as the error from year-month-day will always take priority. + if let Some(ParsedItem(input, ordinal)) = dayo(input) { + *parsed = parsed + .with_year(year) + .ok_or(InvalidComponent("year"))? + .with_ordinal(ordinal) + .ok_or(InvalidComponent("ordinal"))?; + return Ok(input); + } + + match (|| { + let input = ascii_char::<b'W'>(input) + .ok_or((false, InvalidLiteral))? + .into_inner(); + let ParsedItem(mut input, week) = + week(input).ok_or((true, InvalidComponent("week")))?; + if extended_kind.is_extended() { + input = ascii_char::<b'-'>(input) + .ok_or((true, InvalidLiteral))? + .into_inner(); + } + let ParsedItem(input, weekday) = + dayk(input).ok_or((true, InvalidComponent("weekday")))?; + Ok(ParsedItem(input, (week, weekday))) + })() { + Ok(ParsedItem(input, (week, weekday))) => { + *parsed = parsed + .with_iso_year(year) + .ok_or(InvalidComponent("year"))? + .with_iso_week_number(week) + .ok_or(InvalidComponent("week"))? + .with_weekday(weekday) + .ok_or(InvalidComponent("weekday"))?; + return Ok(input); + } + Err((false, _err)) => {} + // This error is more accurate than the one from year-month-day. + Err((true, err)) => ret_error = err, + } + + Err(ret_error.into()) + } + } + + // Basic: ["T"][hour][min][sec] + // Extended: ["T"][hour][":"][min][":"][sec] + // Reduced precision: components after [hour] (including their preceding separator) can be + // omitted. ["T"] can be omitted if there is no date present. + /// Parse a time in the basic or extended format. Reduced precision is permitted. + pub(crate) fn parse_time<'a>( + parsed: &'a mut Parsed, + extended_kind: &'a mut ExtendedKind, + date_is_present: bool, + ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a { + move |mut input| { + if date_is_present { + input = ascii_char::<b'T'>(input) + .ok_or(InvalidLiteral)? + .into_inner(); + } + + let ParsedItem(mut input, hour) = float(input).ok_or(InvalidComponent("hour"))?; + match hour { + (hour, None) => parsed.set_hour_24(hour).ok_or(InvalidComponent("hour"))?, + (hour, Some(fractional_part)) => { + *parsed = parsed + .with_hour_24(hour) + .ok_or(InvalidComponent("hour"))? + .with_minute((fractional_part * Second.per(Minute) as f64) as _) + .ok_or(InvalidComponent("minute"))? + .with_second( + (fractional_part * Second.per(Hour) as f64 % Minute.per(Hour) as f64) + as _, + ) + .ok_or(InvalidComponent("second"))? + .with_subsecond( + (fractional_part * Nanosecond.per(Hour) as f64 + % Nanosecond.per(Second) as f64) as _, + ) + .ok_or(InvalidComponent("subsecond"))?; + return Ok(input); + } + }; + + if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) { + extended_kind + .coerce_extended() + .ok_or(InvalidComponent("minute"))?; + input = new_input; + }; + + let mut input = match float(input) { + Some(ParsedItem(input, (minute, None))) => { + extended_kind.coerce_basic(); + parsed + .set_minute(minute) + .ok_or(InvalidComponent("minute"))?; + input + } + Some(ParsedItem(input, (minute, Some(fractional_part)))) => { + // `None` is valid behavior, so don't error if this fails. + extended_kind.coerce_basic(); + *parsed = parsed + .with_minute(minute) + .ok_or(InvalidComponent("minute"))? + .with_second((fractional_part * Second.per(Minute) as f64) as _) + .ok_or(InvalidComponent("second"))? + .with_subsecond( + (fractional_part * Nanosecond.per(Minute) as f64 + % Nanosecond.per(Second) as f64) as _, + ) + .ok_or(InvalidComponent("subsecond"))?; + return Ok(input); + } + // colon was present, so minutes are required + None if extended_kind.is_extended() => { + return Err(error::Parse::ParseFromDescription(InvalidComponent( + "minute", + ))); + } + None => { + // Missing components are assumed to be zero. + *parsed = parsed + .with_minute(0) + .ok_or(InvalidComponent("minute"))? + .with_second(0) + .ok_or(InvalidComponent("second"))? + .with_subsecond(0) + .ok_or(InvalidComponent("subsecond"))?; + return Ok(input); + } + }; + + if extended_kind.is_extended() { + match ascii_char::<b':'>(input) { + Some(ParsedItem(new_input, ())) => input = new_input, + None => { + *parsed = parsed + .with_second(0) + .ok_or(InvalidComponent("second"))? + .with_subsecond(0) + .ok_or(InvalidComponent("subsecond"))?; + return Ok(input); + } + } + } + + let (input, second, subsecond) = match float(input) { + Some(ParsedItem(input, (second, None))) => (input, second, 0), + Some(ParsedItem(input, (second, Some(fractional_part)))) => ( + input, + second, + round(fractional_part * Nanosecond.per(Second) as f64) as _, + ), + None if extended_kind.is_extended() => { + return Err(error::Parse::ParseFromDescription(InvalidComponent( + "second", + ))); + } + // Missing components are assumed to be zero. + None => (input, 0, 0), + }; + *parsed = parsed + .with_second(second) + .ok_or(InvalidComponent("second"))? + .with_subsecond(subsecond) + .ok_or(InvalidComponent("subsecond"))?; + + Ok(input) + } + } + + // Basic: [±][hour][min] or ["Z"] + // Extended: [±][hour][":"][min] or ["Z"] + // Reduced precision: [±][hour] or ["Z"] + /// Parse a UTC offset in the basic or extended format. Reduced precision is supported. + pub(crate) fn parse_offset<'a>( + parsed: &'a mut Parsed, + extended_kind: &'a mut ExtendedKind, + ) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a { + move |input| { + if let Some(ParsedItem(input, ())) = ascii_char::<b'Z'>(input) { + *parsed = parsed + .with_offset_hour(0) + .ok_or(InvalidComponent("offset hour"))? + .with_offset_minute_signed(0) + .ok_or(InvalidComponent("offset minute"))? + .with_offset_second_signed(0) + .ok_or(InvalidComponent("offset second"))?; + return Ok(input); + } + + let ParsedItem(input, sign) = sign(input).ok_or(InvalidComponent("offset hour"))?; + let mut input = hour(input) + .and_then(|parsed_item| { + parsed_item.consume_value(|hour| { + parsed.set_offset_hour(if sign == b'-' { + -(hour as i8) + } else { + hour as _ + }) + }) + }) + .ok_or(InvalidComponent("offset hour"))?; + + if extended_kind.maybe_extended() { + if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) { + extended_kind + .coerce_extended() + .ok_or(InvalidComponent("offset minute"))?; + input = new_input; + }; + } + + match min(input) { + Some(ParsedItem(new_input, min)) => { + input = new_input; + parsed + .set_offset_minute_signed(if sign == b'-' { + -(min as i8) + } else { + min as _ + }) + .ok_or(InvalidComponent("offset minute"))?; + } + None => { + // Omitted offset minute is assumed to be zero. + parsed.set_offset_minute_signed(0); + } + } + + // If `:` was present, the format has already been set to extended. As such, this call + // will do nothing in that case. If there wasn't `:` but minutes were + // present, we know it's the basic format. Do not use `?` on the call, as + // returning `None` is valid behavior. + extended_kind.coerce_basic(); + + Ok(input) + } + } +} + +/// Round wrapper that uses hardware implementation if `std` is available, falling back to manual +/// implementation for `no_std` +fn round(value: f64) -> f64 { + #[cfg(feature = "std")] + { + value.round() + } + #[cfg(not(feature = "std"))] + { + round_impl(value) + } +} + +#[cfg(not(feature = "std"))] +#[allow(clippy::missing_docs_in_private_items)] +fn round_impl(value: f64) -> f64 { + debug_assert!(value.is_sign_positive() && !value.is_nan()); + + let f = value % 1.; + if f < 0.5 { value - f } else { value - f + 1. } +} |