diff --git a/src/format/parse_iso8601.rs b/src/format/parse_iso8601.rs index b8bcef2936..835083b92e 100644 --- a/src/format/parse_iso8601.rs +++ b/src/format/parse_iso8601.rs @@ -1,5 +1,54 @@ use super::scan; -use super::{ParseResult, INVALID}; +use super::{ParseResult, INVALID, OUT_OF_RANGE}; + +/// Helper type for parsing decimals (as in an ISO 8601 duration). +#[derive(Copy, Clone)] +struct Decimal { + base: u32, + fraction: Option, +} + +impl Decimal { + fn parse(s: &str) -> ParseResult<(&str, Self)> { + let (s, num) = scan::number(s, 1, 10)?; + let (s, frac) = match Fraction::parse(s) { + Ok((s, frac)) => (s, Some(frac)), + Err(_) => (s, None), + }; + let result = + Decimal { base: u32::try_from(num).map_err(|_| OUT_OF_RANGE)?, fraction: frac }; + Ok((s, result)) + } + + /// Multiplying this `Decimal` with `unit`. + /// + /// Returns `None` on out of range. + fn mul(&self, unit: u32) -> ParseResult { + let frac = self.fraction.unwrap_or(Fraction(0)).mul(unit as u64) as u32; + self.base.checked_mul(unit).and_then(|n| n.checked_add(frac)).ok_or(OUT_OF_RANGE) + } + + /// Returns the result of multiplying this `Decimal` with `unit`. + /// + /// Returns two integers to represent the whole number and the fraction as nanos. + fn mul_with_nanos(&self, unit: u64) -> ParseResult<(u64, u32)> { + let (whole_from_rounding, fraction_as_nanos) = + self.fraction.unwrap_or(Fraction(0)).mul_with_nanos(unit); + let whole = (self.base as u64) + .checked_mul(unit) + .and_then(|n| n.checked_add(whole_from_rounding as u64)) + .ok_or(OUT_OF_RANGE)?; + Ok((whole, fraction_as_nanos as u32)) + } + + /// Returns the value of this `Decimal` if it is an integer, otherwise `None`. + fn integer(&self) -> ParseResult { + match self.fraction { + None => Ok(self.base), + _ => Err(INVALID), + } + } +} /// Helper type for parsing fractional numbers. ///