#[derive(Copy, Clone, Debug, PartialEq, Default)]
#[non_exhaustive]
pub struct DateFromFieldsOptions {
pub overflow: Option<Overflow>,
pub missing_fields_strategy: Option<MissingFieldsStrategy>,
}
#[derive(Copy, Clone, PartialEq, Debug, Default)]
#[non_exhaustive]
pub struct DateAddOptions {
pub overflow: Option<Overflow>,
}
#[derive(Copy, Clone, PartialEq, Debug, Default)]
#[non_exhaustive]
pub struct DateDifferenceOptions {
pub largest_unit: Option<DateDurationUnit>,
}
#[derive(Copy, Clone, Debug, PartialEq, Default)]
#[non_exhaustive]
pub enum Overflow {
Constrain,
#[default]
Reject,
}
#[derive(Copy, Clone, Debug, PartialEq, Default)]
#[non_exhaustive]
pub enum MissingFieldsStrategy {
#[default]
Reject,
Ecma,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[allow(clippy::exhaustive_enums)] pub enum DateDurationUnit {
Years,
Months,
Weeks,
Days,
}
#[cfg(test)]
mod tests {
use crate::{
error::DateFromFieldsError,
types::{DateFields, Month},
Date, Gregorian,
};
use itertools::Itertools;
use std::collections::{BTreeMap, BTreeSet};
use super::*;
#[test]
#[allow(clippy::field_reassign_with_default)] fn test_missing_fields_strategy() {
let valid_year_field_sets = [
&["era", "era_year"][..],
&["extended_year"][..],
&["era", "era_year", "extended_year"][..],
]
.into_iter()
.map(|field_names| field_names.iter().copied().collect())
.collect::<Vec<BTreeSet<&str>>>();
let valid_month_field_sets = [
&["month_code"][..],
&["month"][..],
&["ordinal_month"][..],
&["month_code", "ordinal_month"][..],
&["month", "ordinal_month"][..],
]
.into_iter()
.map(|field_names| field_names.iter().copied().collect())
.collect::<Vec<BTreeSet<&str>>>();
let valid_day_field_sets = [&["day"][..]]
.into_iter()
.map(|field_names| field_names.iter().copied().collect())
.collect::<Vec<BTreeSet<&str>>>();
let all_valid_field_sets = valid_year_field_sets
.iter()
.cartesian_product(valid_month_field_sets.iter())
.cartesian_product(valid_day_field_sets.iter())
.map(|((year_fields, month_fields), day_fields)| {
year_fields
.iter()
.chain(month_fields.iter())
.chain(day_fields.iter())
.copied()
.collect::<BTreeSet<&str>>()
})
.collect::<BTreeSet<BTreeSet<&str>>>();
let field_sets_without_day = valid_year_field_sets
.iter()
.cartesian_product(valid_month_field_sets.iter())
.map(|(year_fields, month_fields)| {
year_fields
.iter()
.chain(month_fields.iter())
.copied()
.collect::<BTreeSet<&str>>()
})
.collect::<BTreeSet<BTreeSet<&str>>>();
let field_sets_without_year = [&["month_code", "day"][..], &["month", "day"][..]]
.into_iter()
.map(|field_names| field_names.iter().copied().collect())
.collect::<Vec<BTreeSet<&str>>>();
let mut field_fns = BTreeMap::<&str, &dyn Fn(&mut DateFields)>::new();
field_fns.insert("era", &|fields| fields.era = Some(b"ad"));
field_fns.insert("era_year", &|fields| fields.era_year = Some(2000));
field_fns.insert("extended_year", &|fields| fields.extended_year = Some(2000));
field_fns.insert("month", &|fields| fields.month = Some(Month::new(4)));
field_fns.insert("month_code", &|fields| fields.month_code = Some(b"M04"));
field_fns.insert("ordinal_month", &|fields| fields.ordinal_month = Some(4));
field_fns.insert("day", &|fields| fields.day = Some(20));
for field_set in field_fns.keys().copied().powerset() {
let field_set = field_set.into_iter().collect::<BTreeSet<&str>>();
let should_succeed_rejecting = all_valid_field_sets.contains(&field_set);
let should_succeed_ecma = should_succeed_rejecting
|| field_sets_without_day.contains(&field_set)
|| field_sets_without_year.contains(&field_set);
let mut fields = Default::default();
for field_name in &field_set {
field_fns.get(field_name).unwrap()(&mut fields);
}
let mut options = DateFromFieldsOptions::default();
options.missing_fields_strategy = Some(MissingFieldsStrategy::Reject);
match Date::try_from_fields(fields, options, Gregorian) {
Ok(_) => assert!(
should_succeed_rejecting,
"Succeeded, but should have rejected: {fields:?}"
),
Err(DateFromFieldsError::NotEnoughFields | DateFromFieldsError::TooManyFields) => {
assert!(
!should_succeed_rejecting,
"Rejected, but should have succeeded: {fields:?}"
)
}
Err(e) => panic!("Unexpected error: {e} for {fields:?}"),
}
let mut options = DateFromFieldsOptions::default();
options.missing_fields_strategy = Some(MissingFieldsStrategy::Ecma);
match Date::try_from_fields(fields, options, Gregorian) {
Ok(_) => assert!(
should_succeed_ecma,
"Succeeded, but should have rejected (ECMA): {fields:?}"
),
Err(DateFromFieldsError::NotEnoughFields | DateFromFieldsError::TooManyFields) => {
assert!(
!should_succeed_ecma,
"Rejected, but should have succeeded (ECMA): {fields:?}"
)
}
Err(e) => panic!("Unexpected error: {e} for {fields:?}"),
}
}
}
#[test]
fn test_constrain_large_months() {
let fields = DateFields {
extended_year: Some(2004),
ordinal_month: Some(15),
day: Some(1),
..Default::default()
};
let options = DateFromFieldsOptions {
overflow: Some(Overflow::Constrain),
..Default::default()
};
let _ = Date::try_from_fields(fields, options, crate::cal::Persian).unwrap();
}
}