///|
priv struct ParserNumber {
  whole : String
  fraction : String
}

///|
fn parser_number(text : String) -> ParserNumber? raise DateError {
  if ["nan", "inf", "infinity"].contains(text.to_lower()) {
    raise DateError::InvalidDate(message="nonfinite numeric date token")
  }
  let chars = text.to_array()
  if chars.is_empty() {
    return None
  }
  let mut seen_dot = false
  let whole = StringBuilder()
  let fraction = StringBuilder()
  let mut digits = 0
  for char in chars {
    if char == '.' && !seen_dot {
      seen_dot = true
      continue
    }
    let digit = match parser_decimal_digit(char) {
      Some(value) => value
      None => return None
    }
    digits += 1
    if seen_dot {
      fraction.write_string(digit.to_string())
    } else {
      whole.write_string(digit.to_string())
    }
  }
  if digits == 0 {
    return None
  }
  let integer = whole.to_string()
  Some({
    whole: if integer == "" {
      "0"
    } else {
      integer
    },
    fraction: fraction.to_string(),
  })
}

///|
fn ParserNumber::integer(self : ParserNumber) -> Int32 raise DateError {
  parser_integer(self.whole)
}

///|
fn ParserNumber::fractional(self : ParserNumber) -> Bool {
  self.fraction.to_array().iter().any(char => char != '0')
}

///|
// Decimal arithmetic here is limited to the default parser's positive
// fractional hours/minutes. Match Decimal's 28 significant digits and
// round-half-even at modulo and multiplication, without binary64 rounding.
fn parser_round_decimal(
  digits : Array[Int32],
  exponent : Int32,
) -> (Array[Int32], Int32) {
  let mut first = 0
  while first < digits.length() && digits[first] == 0 {
    first += 1
  }
  let value = digits[first:].to_owned()
  if value.length() <= 28 {
    return (value, exponent)
  }
  let removed = value.length() - 28
  let result = value[:28].to_owned()
  let next = value[28]
  let round_up = next > 5 ||
    (
      next == 5 &&
      (result[27] % 2 == 1 || value[29:].iter().any(digit => digit != 0))
    )
  if round_up {
    let mut index = result.length() - 1
    while index >= 0 && result[index] == 9 {
      result[index] = 0
      index -= 1
    }
    if index < 0 {
      result.insert(0, 1)
    } else {
      result[index] += 1
    }
  }
  (result, exponent + removed)
}

///|
fn parser_fraction_times60(fraction : String) -> Int32 {
  let digits = fraction.to_array().map(char => char.to_int() - 48)
  let (digits, exponent) = parser_round_decimal(digits, -digits.length())
  let product = digits.copy()
  let mut carry = 0
  for index = product.length() - 1; index >= 0; index = index - 1 {
    let value = product[index] * 60 + carry
    product[index] = value % 10
    carry = value / 10
  }
  while carry > 0 {
    product.insert(0, carry % 10)
    carry /= 10
  }
  let (product, exponent) = parser_round_decimal(product, exponent)
  let length = product.length() + exponent
  let mut result = 0
  for index in 0.. (Int32, Int32?) raise DateError {
  (
    number.integer(),
    if number.fractional() {
      Some(parser_fraction_times60(number.fraction))
    } else {
      None
    },
  )
}

///|
fn parser_seconds(text : String) -> (Int32, Int32) raise DateError {
  let number = parser_number(text).unwrap_or_else(() => {
    raise DateError::InvalidDate(message="invalid seconds token")
  })
  let digits = number.fraction.to_array()
  let mut micros = 0
  for index in 0..<6 {
    micros = micros * 10 +
      (if index < digits.length() { digits[index].to_int() - 48 } else { 0 })
  }
  (number.integer(), micros)
}

///|
fn ParserYmd::append_numeric(
  self : ParserYmd,
  number : ParserNumber,
) -> Unit raise DateError {
  if number.integer() == 100 && number.fractional() {
    self.century = true
    self.append_value(100, Some(Y))
  } else {
    self.append_number(number.integer())
  }
}

///|
fn ParserYmd::could_be_numeric_day(
  self : ParserYmd,
  number : ParserNumber,
) -> Bool raise DateError {
  self.could_be_day(number.integer()) &&
  (!number.fractional() || self.could_be_day(number.integer() + 1))
}