///|
/// 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
}