use std::collections::HashMap;
use chrono::{Datelike, NaiveDate};
use crate::temporal_field::TemporalField;
pub struct Chronology {}
impl Chronology {
pub fn resolve_date(
field_values: &mut HashMap<TemporalField, i64>,
) -> Result<Option<NaiveDate>, String> {
let resolved = Self::resolve_year_of_era(field_values)?;
if let Some(resolved) = resolved {
return Ok(Some(resolved));
}
if field_values.contains_key(&TemporalField::Year) {
if field_values.contains_key(&TemporalField::MonthOfYear)
&& field_values.contains_key(&TemporalField::DayOfMonth)
{
return Self::resolve_ymd(field_values);
}
if field_values.contains_key(&TemporalField::DayOfYear) {
return Self::resolve_yd(field_values);
}
}
Ok(None)
}
pub fn resolve_year_of_era(
field_values: &mut HashMap<TemporalField, i64>,
) -> Result<Option<NaiveDate>, String> {
let yoe_long = field_values.remove(&TemporalField::YearOfEra);
if let Some(yoe_long) = yoe_long {
let yoe = TemporalField::YearOfEra
.range()
.check_valid_int_value(yoe_long)?;
if let Some(era) = field_values.remove(&TemporalField::Era) {
let era = TemporalField::Era.range().check_valid_int_value(era)?;
Self::add_field_value(
field_values,
TemporalField::Year,
Self::proleptic_year(era, yoe),
)?;
} else if let Some(&year) = field_values.get(&TemporalField::Year) {
let year = TemporalField::Year.range().check_valid_int_value(year)?;
let chrono_date = NaiveDate::from_yo_opt(year, 1)
.ok_or("Could not construct date from given year")?;
Self::add_field_value(
field_values,
TemporalField::Year,
Self::proleptic_year(if chrono_date.year() > 0 { 1 } else { 0 }, yoe),
)?;
} else {
Self::add_field_value(
field_values,
TemporalField::Year,
Self::proleptic_year(1, yoe),
)?;
}
} else if field_values.contains_key(&TemporalField::Era) {
TemporalField::Era
.range()
.check_valid_int_value(field_values[&TemporalField::Era])?; }
Ok(None)
}
fn resolve_ymd(
field_values: &mut HashMap<TemporalField, i64>,
) -> Result<Option<NaiveDate>, String> {
let y = TemporalField::Year.range().check_valid_int_value(
field_values
.remove(&TemporalField::Year)
.ok_or("Expected Year to be present")?,
)?;
let moy = TemporalField::MonthOfYear.range().check_valid_int_value(
field_values
.remove(&TemporalField::MonthOfYear)
.ok_or("Expected MonthOfYear to be present")?,
)?;
let dom_range = TemporalField::DayOfMonth.range();
let dom = dom_range.check_valid_int_value(
field_values
.remove(&TemporalField::DayOfMonth)
.ok_or("Expected DayOfMonth to be present")?,
)?;
if let Some(date) = NaiveDate::from_ymd_opt(y, moy as u32, dom as u32) {
Ok(Some(date))
} else {
let date = Self::last_day_of_month(y, moy as u32)
.ok_or("Invalid date: could not find last day of month")?;
Ok(Some(date))
}
}
fn resolve_yd(
field_values: &mut HashMap<TemporalField, i64>,
) -> Result<Option<NaiveDate>, String> {
let y = TemporalField::Year.range().check_valid_int_value(
field_values
.remove(&TemporalField::Year)
.ok_or("Expected Year to be present")?,
)?;
let doy = TemporalField::DayOfYear.range().check_valid_int_value(
field_values
.remove(&TemporalField::DayOfYear)
.ok_or("Expected DayOfYear to be present")?,
)?;
let date = NaiveDate::from_yo_opt(y, doy as u32)
.ok_or("Invalid date: could not construct date from year and day of year")?;
Ok(Some(date))
}
fn proleptic_year(era: i32, yoe: i32) -> i64 {
if era == 0 {
return (1 - yoe) as i64;
}
yoe as i64
}
fn add_field_value(
field_values: &mut HashMap<TemporalField, i64>,
field: TemporalField,
value: i64,
) -> Result<(), String> {
let old = field_values.get(&field);
if let Some(&old_value) = old {
if old_value != value {
return Err(format!(
"Field {:?} already has value {}, cannot set to {}",
field, old_value, value
));
}
}
field_values.insert(field, value);
Ok(())
}
fn last_day_of_month(year: i32, month: u32) -> Option<NaiveDate> {
let days_in_month = match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => {
if NaiveDate::from_ymd_opt(year, 2, 29).is_some() {
29
} else {
28
}
}
_ => return None,
};
NaiveDate::from_ymd_opt(year, month, days_in_month)
}
}