| //! Parse parts of an ISO 8601-formatted value. | 
 |  | 
 | use num_conv::prelude::*; | 
 |  | 
 | 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. | 
 |     #[allow(clippy::needless_pass_by_ref_mut)] // rust-lang/rust-clippy#11620 | 
 |     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.cast_signed() | 
 |                         } else { | 
 |                             hour.cast_signed() | 
 |                         }) | 
 |                     }) | 
 |                 }) | 
 |                 .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.cast_signed() | 
 |                         } else { | 
 |                             min.cast_signed() | 
 |                         }) | 
 |                         .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. } | 
 | } |