///|
/// Calendar date used to validate morning measurement records.
pub(all) struct DateParts {
  year : Int
  month : Int
  day : Int
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Parse a strict YYYY-MM-DD date.
pub fn parse_date(value : String) -> DateParts? {
  if value.length() != 10 || value[4] != 45 || value[7] != 45 {
    return None
  }
  let year_text = value[0:4].to_owned()
  let month_text = value[5:7].to_owned()
  let day_text = value[8:10].to_owned()
  let year = parse_decimal(year_text)
  let month = parse_decimal(month_text)
  let day = parse_decimal(day_text)
  match (year, month, day) {
    (Some(y), Some(m), Some(d)) =>
      if is_valid_date(y, m, d) {
        Some({ year: y, month: m, day: d })
      } else {
        None
      }
    _ => None
  }
}

///|
/// Parse a non-empty ASCII decimal string.
fn parse_decimal(value : String) -> Int? {
  if value.length() == 0 {
    return None
  }
  let mut result = 0
  for character in value {
    if character < '0' || character > '9' {
      return None
    }
    result = result * 10 + (character.to_int() - '0'.to_int())
  }
  Some(result)
}

///|
/// Return whether a Gregorian year is a leap year.
pub fn is_leap_year(year : Int) -> Bool {
  year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)
}

///|
/// Return the number of days in a month.
pub fn days_in_month(year : Int, month : Int) -> Int {
  match month {
    1 | 3 | 5 | 7 | 8 | 10 | 12 => 31
    4 | 6 | 9 | 11 => 30
    2 => if is_leap_year(year) { 29 } else { 28 }
    _ => 0
  }
}

///|
/// Validate a Gregorian date.
pub fn is_valid_date(year : Int, month : Int, day : Int) -> Bool {
  year >= 1 &&
  month >= 1 &&
  month <= 12 &&
  day >= 1 &&
  day <= days_in_month(year, month)
}

///|
/// Convert a valid date to a monotonically increasing day number.
pub fn date_to_ordinal(date : DateParts) -> Int {
  let mut days = 0
  for year in 1.. Int {
  date_to_ordinal(right) - date_to_ordinal(left)
}

///|
/// Return whether dates are strictly increasing.
pub fn dates_are_ordered(values : Array[String]) -> Bool {
  if values.length() == 0 {
    return true
  }
  if values.length() == 1 {
    return parse_date(values[0]) is Some(_)
  }
  let mut previous = parse_date(values[0])
  if previous is None {
    return false
  }
  for i in 1.. {
        if date_distance(left, right) <= 0 {
          return false
        }
        previous = Some(right)
      }
      _ => return false
    }
  }
  true
}

///|
/// Count missing calendar days in an ordered date series.
pub fn missing_calendar_days(values : Array[String]) -> Int {
  if values.length() <= 1 {
    return 0
  }
  let mut missing = 0
  for i in 1.. {
        let distance = date_distance(left, right)
        if distance > 1 {
          missing += distance - 1
        }
      }
      _ => ()
    }
  }
  missing
}