use calendrical_calculations::rata_die::RataDie;
use crate::duration::DateDuration;
use crate::error::{
range_check, DateAddError, DateFromFieldsError, DateNewError, EcmaReferenceYearError,
LunisolarDateError, MonthError, UnknownEraError,
};
use crate::options::{DateAddOptions, DateDifferenceOptions, DateDurationUnit};
use crate::options::{DateFromFieldsOptions, MissingFieldsStrategy, Overflow};
use crate::types::{DateFields, Month};
use crate::{types, Calendar, DateError, RangeError};
use core::cmp::Ordering;
use core::fmt::Debug;
use core::hash::{Hash, Hasher};
use core::ops::RangeInclusive;
use core::ops::Sub;
pub(crate) const CONSTRUCTOR_YEAR_RANGE: RangeInclusive<i32> = -9999..=9999;
pub(crate) const VALID_RD_RANGE: RangeInclusive<RataDie> =
calendrical_calculations::gregorian::fixed_from_gregorian(-999999, 1, 1)
..=calendrical_calculations::gregorian::fixed_from_gregorian(999999, 12, 31);
pub(crate) const GENEROUS_YEAR_RANGE: RangeInclusive<i32> = -1_040_000..=1_040_000;
pub(crate) const GENEROUS_MAX_YEARS: u32 =
(*GENEROUS_YEAR_RANGE.end() - *GENEROUS_YEAR_RANGE.start()) as u32;
pub(crate) const GENEROUS_MAX_MONTHS: u32 = GENEROUS_MAX_YEARS * 13;
pub(crate) const GENEROUS_MAX_DAYS: u32 = GENEROUS_MAX_MONTHS * 31;
pub(crate) const SAFE_YEAR_RANGE: RangeInclusive<i32> = -8_000_000..=8_000_000;
#[derive(Debug)]
pub(crate) struct ArithmeticDate<C: DateFieldsResolver>(<C::YearInfo as PackWithMD>::Packed);
impl<C: DateFieldsResolver> Copy for ArithmeticDate<C> {}
impl<C: DateFieldsResolver> Clone for ArithmeticDate<C> {
fn clone(&self) -> Self {
*self
}
}
impl<C: DateFieldsResolver> PartialEq for ArithmeticDate<C> {
fn eq(&self, other: &Self) -> bool {
self.year() == other.year() && self.month() == other.month() && self.day() == other.day()
}
}
impl<C: DateFieldsResolver> Eq for ArithmeticDate<C> {}
impl<C: DateFieldsResolver> Ord for ArithmeticDate<C> {
fn cmp(&self, other: &Self) -> Ordering {
self.year()
.cmp(&other.year())
.then(self.month().cmp(&other.month()))
.then(self.day().cmp(&other.day()))
}
}
impl<C: DateFieldsResolver> PartialOrd for ArithmeticDate<C> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<C: DateFieldsResolver> Hash for ArithmeticDate<C> {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.year().hash(state);
self.month().hash(state);
self.day().hash(state);
}
}
#[derive(Debug)]
struct UncheckedArithmeticDate<C: DateFieldsResolver> {
year: C::YearInfo,
ordinal_month: u8,
day: u8,
}
impl<C: DateFieldsResolver> UncheckedArithmeticDate<C> {
fn into_checked(self) -> Option<ArithmeticDate<C>> {
let rd = C::to_rata_die_inner(self.year, self.ordinal_month, self.day);
if !VALID_RD_RANGE.contains(&rd) {
return None;
}
Some(ArithmeticDate::new_unchecked(
self.year,
self.ordinal_month,
self.day,
))
}
}
#[allow(dead_code)] pub(crate) const MAX_ITERS_FOR_DAYS_OF_MONTH: u8 = 33;
pub(crate) trait PackWithMD: Copy {
type Packed: Copy + Debug;
fn pack(self, month: u8, day: u8) -> Self::Packed;
fn unpack_year(packed: Self::Packed) -> Self;
fn unpack_month(packed: Self::Packed) -> u8;
fn unpack_day(packed: Self::Packed) -> u8;
}
impl PackWithMD for i32 {
type Packed = [u8; 4];
fn pack(self, month: u8, day: u8) -> Self::Packed {
(self << 9 | i32::from(month) << 5 | i32::from(day)).to_le_bytes()
}
fn unpack_year(packed: Self::Packed) -> Self {
let packed = i32::from_le_bytes(packed);
packed >> 9
}
fn unpack_month(packed: Self::Packed) -> u8 {
let packed = i32::from_le_bytes(packed);
(packed >> 5 & 0b1111) as u8
}
fn unpack_day(packed: Self::Packed) -> u8 {
let packed = i32::from_le_bytes(packed);
(packed & 0b11111) as u8
}
}
pub(crate) trait DateFieldsResolver: Calendar {
type YearInfo: Copy + Debug + Hash + Ord + Sub<Output = i32> + PackWithMD;
fn days_in_provided_month(year: Self::YearInfo, month: u8) -> u8;
fn extended_year_from_era_year_unchecked(
&self,
era: &[u8],
era_year: i32,
) -> Result<i32, UnknownEraError>;
fn year_info_from_extended(&self, extended_year: i32) -> Self::YearInfo;
fn extended_from_year_info(&self, year_info: Self::YearInfo) -> i32;
fn reference_year_from_month_day(
&self,
month: Month,
day: u8,
) -> Result<Self::YearInfo, EcmaReferenceYearError>;
fn months_in_provided_year(_year: Self::YearInfo) -> u8 {
12
}
#[inline]
fn min_months_from(_start: Self::YearInfo, years: i32) -> i32 {
12 * years
}
#[inline]
fn ordinal_from_month(
&self,
year: Self::YearInfo,
month: Month,
_overflow: Overflow,
) -> Result<u8, MonthError> {
match (month.number(), month.is_leap()) {
(month_number, false)
if (1..=Self::months_in_provided_year(year)).contains(&month_number) =>
{
Ok(month_number)
}
_ => Err(MonthError::NotInCalendar),
}
}
#[inline]
fn month_from_ordinal(&self, _year: Self::YearInfo, ordinal_month: u8) -> Month {
Month::new_unchecked(ordinal_month, false)
}
fn to_rata_die_inner(year: Self::YearInfo, month: u8, day: u8) -> RataDie;
}
impl<C: DateFieldsResolver> ArithmeticDate<C> {
pub(crate) fn to_rata_die(self) -> RataDie {
C::to_rata_die_inner(self.year(), self.month(), self.day())
}
pub(crate) fn year(self) -> C::YearInfo {
C::YearInfo::unpack_year(self.0)
}
pub(crate) fn month(self) -> u8 {
C::YearInfo::unpack_month(self.0)
}
pub(crate) fn day(self) -> u8 {
C::YearInfo::unpack_day(self.0)
}
#[inline]
pub(crate) fn new_unchecked(year: C::YearInfo, month: u8, day: u8) -> Self {
ArithmeticDate(C::YearInfo::pack(year, month, day))
}
pub(crate) fn cast<C2: DateFieldsResolver<YearInfo = C::YearInfo>>(self) -> ArithmeticDate<C2> {
ArithmeticDate::new_unchecked(self.year(), self.month(), self.day())
}
pub(crate) fn from_input_year_month_code_day(
year: types::YearInput,
month: Month,
day: u8,
calendar: &C,
) -> Result<Self, DateNewError> {
let extended_year = match year {
types::YearInput::Extended(y) => {
if !CONSTRUCTOR_YEAR_RANGE.contains(&y) {
return Err(DateNewError::InvalidYear);
}
y
}
types::YearInput::EraYear(era, y) => {
if !CONSTRUCTOR_YEAR_RANGE.contains(&y) {
return Err(DateNewError::InvalidYear);
}
calendar
.extended_year_from_era_year_unchecked(era.as_bytes(), y)
.map_err(|_| DateNewError::InvalidEra)?
}
};
let year = calendar.year_info_from_extended(extended_year);
let month = calendar
.ordinal_from_month(year, month, Overflow::Reject)
.map_err(|e| match e {
MonthError::NotInCalendar => DateNewError::MonthNotInCalendar,
MonthError::NotInYear => DateNewError::MonthNotInYear,
})?;
let max_day = C::days_in_provided_month(year, month);
if day < 1 || day > max_day {
return Err(DateNewError::InvalidDay { max: max_day });
}
Ok(ArithmeticDate::new_unchecked(year, month, day))
}
pub(crate) fn try_from_ymd_lunisolar(
year: i32,
month: Month,
day: u8,
calendar: &C,
) -> Result<Self, LunisolarDateError> {
if !CONSTRUCTOR_YEAR_RANGE.contains(&year) {
return Err(LunisolarDateError::InvalidYear);
}
let year = calendar.year_info_from_extended(year);
let month = match calendar.ordinal_from_month(year, month, Overflow::Reject) {
Ok(month) => month,
Err(MonthError::NotInCalendar) => return Err(LunisolarDateError::MonthNotInCalendar),
Err(MonthError::NotInYear) => return Err(LunisolarDateError::MonthNotInYear),
};
let max_day = C::days_in_provided_month(year, month);
if !(1..=max_day).contains(&day) {
return Err(LunisolarDateError::InvalidDay { max: max_day });
}
Ok(Self::new_unchecked(year, month, day))
}
pub(crate) fn from_fields(
fields: DateFields,
options: DateFromFieldsOptions,
calendar: &C,
) -> Result<Self, DateFromFieldsError> {
let missing_fields_strategy = options.missing_fields_strategy.unwrap_or_default();
let overflow = options.overflow.unwrap_or(Overflow::Reject);
let day = match fields.day {
Some(day) => day,
None => match missing_fields_strategy {
MissingFieldsStrategy::Reject => return Err(DateFromFieldsError::NotEnoughFields),
MissingFieldsStrategy::Ecma => {
if fields.extended_year.is_some() || fields.era_year.is_some() {
1
} else {
return Err(DateFromFieldsError::NotEnoughFields);
}
}
},
};
if fields.month_code.is_none() && fields.ordinal_month.is_none() && fields.month.is_none() {
return Err(DateFromFieldsError::NotEnoughFields);
}
if fields.month_code.is_some() && fields.month.is_some() {
return Err(DateFromFieldsError::TooManyFields);
}
let mut valid_month = None;
let year = match (fields.era, fields.era_year) {
(None, None) => match fields.extended_year {
Some(extended_year) => {
if !GENEROUS_YEAR_RANGE.contains(&extended_year) {
return Err(DateFromFieldsError::Overflow);
}
calendar.year_info_from_extended(extended_year)
}
None => match missing_fields_strategy {
MissingFieldsStrategy::Reject => {
return Err(DateFromFieldsError::NotEnoughFields)
}
MissingFieldsStrategy::Ecma => {
let (m, d) = match (fields.month, fields.month_code, fields.ordinal_month) {
(Some(month), _, None) => (month, day),
(_, Some(month_code), None) => {
let validated = Month::try_from_utf8(month_code)?;
valid_month = Some(validated);
(validated, day)
}
_ => return Err(DateFromFieldsError::NotEnoughFields),
};
let ref_year = calendar.reference_year_from_month_day(m, d);
if ref_year.err() == Some(EcmaReferenceYearError::UseRegularIfConstrain)
&& overflow == Overflow::Constrain
{
let new_valid_month = Month::new(m.number());
valid_month = Some(new_valid_month);
calendar.reference_year_from_month_day(new_valid_month, d)?
} else {
ref_year?
}
}
},
},
(Some(era), Some(era_year)) => {
if !GENEROUS_YEAR_RANGE.contains(&era_year) {
return Err(DateFromFieldsError::Overflow);
}
let extended_year =
calendar.extended_year_from_era_year_unchecked(era, era_year)?;
let year = calendar.year_info_from_extended(extended_year);
if let Some(extended_year) = fields.extended_year {
if calendar.extended_from_year_info(year) != extended_year {
return Err(DateFromFieldsError::InconsistentYear);
}
}
year
}
(Some(_), None) | (None, Some(_)) => return Err(DateFromFieldsError::NotEnoughFields),
};
let month = match (fields.month_code, fields.month) {
(_, Some(month)) => {
let computed_month = calendar.ordinal_from_month(year, month, overflow)?;
if let Some(ordinal_month) = fields.ordinal_month {
if computed_month != ordinal_month {
return Err(DateFromFieldsError::InconsistentMonth);
}
}
computed_month
}
(Some(month_code), _) => {
let validated = match valid_month {
Some(validated) => validated,
None => Month::try_from_utf8(month_code)?,
};
let computed_month = calendar.ordinal_from_month(year, validated, overflow)?;
if let Some(ordinal_month) = fields.ordinal_month {
if computed_month != ordinal_month {
return Err(DateFromFieldsError::InconsistentMonth);
}
}
computed_month
}
(None, None) => match fields.ordinal_month {
Some(month) => month,
None => {
debug_assert!(false, "Already checked above");
return Err(DateFromFieldsError::NotEnoughFields);
}
},
};
let max_month = C::months_in_provided_year(year);
let month = if matches!(overflow, Overflow::Constrain) {
month.clamp(1, max_month)
} else if (1..=max_month).contains(&month) {
month
} else {
return Err(DateFromFieldsError::InvalidOrdinalMonth { max: max_month });
};
let max_day = C::days_in_provided_month(year, month);
let day = if matches!(overflow, Overflow::Constrain) {
day.clamp(1, max_day)
} else if (1..=max_day).contains(&day) {
day
} else {
return Err(DateFromFieldsError::InvalidDay { max: max_day });
};
let rd = C::to_rata_die_inner(year, month, day);
if !VALID_RD_RANGE.contains(&rd) {
return Err(DateFromFieldsError::Overflow);
}
Ok(Self::new_unchecked(year, month, day))
}
pub(crate) fn from_year_month_day(
year: i32,
month: u8,
day: u8,
cal: &C,
) -> Result<Self, RangeError> {
range_check(year, "year", CONSTRUCTOR_YEAR_RANGE)?;
let year_info = cal.year_info_from_extended(year);
range_check(month, "month", 1..=C::months_in_provided_year(year_info))?;
range_check(day, "day", 1..=C::days_in_provided_month(year_info, month))?;
Ok(ArithmeticDate::new_unchecked(year_info, month, day))
}
pub(crate) fn from_era_year_month_day(
era: &str,
year: i32,
month: u8,
day: u8,
cal: &C,
) -> Result<Self, DateError> {
range_check(year, "year", CONSTRUCTOR_YEAR_RANGE)?;
let extended_year = cal.extended_year_from_era_year_unchecked(era.as_bytes(), year)?;
let year_info = cal.year_info_from_extended(extended_year);
range_check(month, "month", 1..=C::months_in_provided_year(year_info))?;
range_check(day, "day", 1..=C::days_in_provided_month(year_info, month))?;
Ok(ArithmeticDate::new_unchecked(year_info, month, day))
}
fn new_balanced(
year: C::YearInfo,
ordinal_month: i32,
day: i32,
cal: &C,
) -> UncheckedArithmeticDate<C> {
let mut resolved_year = year;
let mut resolved_month = ordinal_month;
let mut months_in_year = C::months_in_provided_year(resolved_year);
while resolved_month <= 0 {
resolved_year =
cal.year_info_from_extended(cal.extended_from_year_info(resolved_year) - 1);
months_in_year = C::months_in_provided_year(resolved_year);
resolved_month += i32::from(months_in_year);
}
while resolved_month > i32::from(months_in_year) {
resolved_month -= i32::from(months_in_year);
resolved_year =
cal.year_info_from_extended(cal.extended_from_year_info(resolved_year) + 1);
months_in_year = C::months_in_provided_year(resolved_year);
}
debug_assert!(u8::try_from(resolved_month).is_ok());
let mut resolved_month = resolved_month as u8;
let mut resolved_day = day;
let mut days_in_month = C::days_in_provided_month(resolved_year, resolved_month);
while resolved_day <= 0 {
resolved_month -= 1;
if resolved_month == 0 {
resolved_year =
cal.year_info_from_extended(cal.extended_from_year_info(resolved_year) - 1);
months_in_year = C::months_in_provided_year(resolved_year);
resolved_month = months_in_year;
}
days_in_month = C::days_in_provided_month(resolved_year, resolved_month);
resolved_day += i32::from(days_in_month);
}
while resolved_day > i32::from(days_in_month) {
resolved_day -= i32::from(days_in_month);
resolved_month += 1;
if resolved_month > months_in_year {
resolved_year =
cal.year_info_from_extended(cal.extended_from_year_info(resolved_year) + 1);
months_in_year = C::months_in_provided_year(resolved_year);
resolved_month = 1;
}
days_in_month = C::days_in_provided_month(resolved_year, resolved_month);
}
debug_assert!(u8::try_from(resolved_day).is_ok());
let resolved_day = resolved_day as u8;
UncheckedArithmeticDate {
year: resolved_year,
ordinal_month: resolved_month,
day: resolved_day,
}
}
#[allow(clippy::collapsible_if, clippy::collapsible_else_if)] fn compare_surpasses_lexicographic(
sign: i32,
year: C::YearInfo,
month: Month,
day: u8,
target: &Self,
cal: &C,
) -> bool {
if year != target.year() {
if sign * (year - target.year()) > 0 {
return true;
}
} else {
let target_month = cal.month_from_ordinal(target.year(), target.month());
if month != target_month {
if sign > 0 {
if month > target_month {
return true;
}
} else {
if month <= target_month {
return true;
}
}
} else if day != target.day() {
if sign * (i32::from(day) - i32::from(target.day())) > 0 {
return true;
}
}
}
false
}
#[allow(clippy::collapsible_if, clippy::collapsible_else_if)] fn compare_surpasses_ordinal(
sign: i32,
year: C::YearInfo,
month: u8,
day: u8,
target: &Self,
) -> bool {
if year != target.year() {
if sign * (year - target.year()) > 0 {
return true;
}
} else if month != target.month() {
if sign * (i32::from(month) - i32::from(target.month())) > 0 {
return true;
}
} else if day != target.day() {
if sign * (i32::from(day) - i32::from(target.day())) > 0 {
return true;
}
}
false
}
#[allow(dead_code)] pub(crate) fn surpasses(
&self,
other: &Self,
duration: DateDuration,
sign: i32,
cal: &C,
) -> bool {
let parts = self;
let cal_date_2 = other;
let y0 = cal.year_info_from_extended(
duration.add_years_to(cal.extended_from_year_info(parts.year())),
);
let base_month = cal.month_from_ordinal(parts.year(), parts.month());
if Self::compare_surpasses_lexicographic(sign, y0, base_month, parts.day(), cal_date_2, cal)
{
return true;
}
let m0_result = cal.ordinal_from_month(y0, base_month, Overflow::Constrain);
let m0 = match m0_result {
Ok(m0) => m0,
Err(_) => {
debug_assert!(
false,
"valid month code for calendar, and constrained to the year"
);
1
}
};
let months_added = Self::new_balanced(y0, duration.add_months_to(m0), 1, cal);
if Self::compare_surpasses_ordinal(
sign,
months_added.year,
months_added.ordinal_month,
parts.day(),
cal_date_2,
) {
return true;
}
if duration.weeks == 0 && duration.days == 0 {
return false;
}
let end_of_month = Self::new_balanced(
months_added.year,
i32::from(months_added.ordinal_month + 1),
0,
cal,
);
let base_day = parts.day();
let regulated_day = if base_day < end_of_month.day {
base_day
} else {
end_of_month.day
};
let balanced_date = Self::new_balanced(
end_of_month.year,
i32::from(end_of_month.ordinal_month),
duration.add_weeks_and_days_to(regulated_day),
cal,
);
Self::compare_surpasses_ordinal(
sign,
balanced_date.year,
balanced_date.ordinal_month,
balanced_date.day,
cal_date_2,
)
}
pub(crate) fn added(
&self,
duration: DateDuration,
cal: &C,
options: DateAddOptions,
) -> Result<Self, DateAddError> {
if duration.years > GENEROUS_MAX_YEARS
|| duration.months > GENEROUS_MAX_MONTHS
|| duration
.weeks
.saturating_mul(7)
.saturating_add(duration.days)
> GENEROUS_MAX_DAYS
{
return Err(DateAddError::Overflow);
}
let overflow = options.overflow.unwrap_or(Overflow::Constrain);
let extended_year = duration.add_years_to(cal.extended_from_year_info(self.year()));
if !GENEROUS_YEAR_RANGE.contains(&extended_year) {
return Err(DateAddError::Overflow);
}
let y0 = cal.year_info_from_extended(extended_year);
let base_month = cal.month_from_ordinal(self.year(), self.month());
let m0_result = cal.ordinal_from_month(y0, base_month, overflow);
let m0 = match m0_result {
Ok(m0) => m0,
Err(MonthError::NotInCalendar) => {
debug_assert!(
false,
"Should never get NotInCalendar when performing arithmetic"
);
return Err(DateAddError::MonthNotInYear);
}
Err(MonthError::NotInYear) => return Err(DateAddError::MonthNotInYear),
};
let end_of_month = Self::new_balanced(y0, duration.add_months_to(m0) + 1, 0, cal);
let base_day = self.day();
let regulated_day = if base_day <= end_of_month.day {
base_day
} else {
if overflow == Overflow::Reject {
return Err(DateAddError::InvalidDay {
max: end_of_month.day,
});
}
end_of_month.day
};
let balanced = Self::new_balanced(
end_of_month.year,
i32::from(end_of_month.ordinal_month),
duration.add_weeks_and_days_to(regulated_day),
cal,
);
balanced.into_checked().ok_or(DateAddError::Overflow)
}
pub(crate) fn until(
&self,
other: &Self,
cal: &C,
options: DateDifferenceOptions,
) -> DateDuration {
if matches!(
options.largest_unit,
Some(DateDurationUnit::Days) | Some(DateDurationUnit::Weeks)
) {
let from = self.to_rata_die();
let to = other.to_rata_die();
let diff = to - from;
let diff = match i32::try_from(diff) {
Ok(d) => d,
Err(_) => {
debug_assert!(false, "rata die diff out of i32 range");
if diff > 0 {
i32::MAX
} else {
i32::MIN
}
}
};
if matches!(options.largest_unit, Some(DateDurationUnit::Weeks)) {
return DateDuration::for_weeks_and_days(diff);
} else {
return DateDuration::for_days(diff);
}
}
let sign = match other.cmp(self) {
Ordering::Greater => 1,
Ordering::Equal => return DateDuration::default(),
Ordering::Less => -1,
};
let mut surpasses_checker: SurpassesChecker<'_, C> =
SurpassesChecker::new(self, other, sign, cal);
let year_diff = other.year() - self.year();
let min_years = if year_diff == 0 { 0 } else { year_diff - sign };
#[cfg(debug_assertions)]
{
let mut debug_checker = SurpassesChecker::new(self, other, sign, cal);
let min_years_valid = !debug_checker.surpasses_years(min_years);
debug_assert!(min_years_valid);
}
let mut years = 0;
if matches!(options.largest_unit, Some(DateDurationUnit::Years)) {
let mut candidate_years = sign;
if min_years != 0 {
candidate_years = min_years
};
while !surpasses_checker.surpasses_years(candidate_years) {
years = candidate_years;
candidate_years += sign;
}
}
surpasses_checker.set_years(years);
let mut months = 0;
if matches!(
options.largest_unit,
Some(DateDurationUnit::Years) | Some(DateDurationUnit::Months)
) {
let mut candidate_months = sign;
if options.largest_unit == Some(DateDurationUnit::Months) && min_years != 0 {
let min_months = C::min_months_from(self.year(), min_years);
#[cfg(debug_assertions)]
{
let mut debug_checker = SurpassesChecker::new(self, other, sign, cal);
debug_checker.surpasses_years(years);
let min_months_valid = !debug_checker.surpasses_months(min_months);
debug_assert!(min_months_valid);
}
candidate_months = min_months
}
while !surpasses_checker.surpasses_months(candidate_months) {
months = candidate_months;
candidate_months += sign;
}
}
surpasses_checker.set_months(months);
let mut weeks = 0;
if matches!(options.largest_unit, Some(DateDurationUnit::Weeks)) {
let mut candidate_weeks = sign;
while !surpasses_checker.surpasses_weeks(candidate_weeks) {
weeks = candidate_weeks;
candidate_weeks += sign;
}
}
surpasses_checker.set_weeks(weeks);
let mut days = 0;
let mut candidate_days = sign;
while !surpasses_checker.surpasses_days(candidate_days) {
days = candidate_days;
candidate_days += sign;
}
DateDuration::from_signed_ymwd(years, months, weeks, days)
}
}
struct SurpassesChecker<'a, C: DateFieldsResolver> {
parts: &'a ArithmeticDate<C>,
cal_date_2: &'a ArithmeticDate<C>,
sign: i32,
cal: &'a C,
y0: <C as DateFieldsResolver>::YearInfo,
m0: u8,
end_of_month: UncheckedArithmeticDate<C>,
regulated_day: u8,
weeks: i32,
}
impl<'a, C: DateFieldsResolver> SurpassesChecker<'a, C> {
fn new(
parts: &'a ArithmeticDate<C>,
cal_date_2: &'a ArithmeticDate<C>,
sign: i32,
cal: &'a C,
) -> Self {
Self {
parts,
cal_date_2,
sign,
cal,
y0: cal.year_info_from_extended(0),
m0: 0,
end_of_month: UncheckedArithmeticDate {
year: cal.year_info_from_extended(0),
ordinal_month: 0,
day: 0,
},
regulated_day: 0,
weeks: 0,
}
}
fn surpasses_years(&mut self, years: i32) -> bool {
self.y0 = self
.cal
.year_info_from_extended(self.cal.extended_from_year_info(self.parts.year()) + years);
let base_month = self
.cal
.month_from_ordinal(self.parts.year(), self.parts.month());
let surpasses_years = ArithmeticDate::<C>::compare_surpasses_lexicographic(
self.sign,
self.y0,
base_month,
self.parts.day(),
self.cal_date_2,
self.cal,
);
let m0_result = self
.cal
.ordinal_from_month(self.y0, base_month, Overflow::Constrain);
self.m0 = match m0_result {
Ok(m0) => m0,
Err(_) => {
debug_assert!(
false,
"valid month code for calendar, and constrained to the year"
);
1
}
};
surpasses_years || self.surpasses_months(0)
}
#[inline]
fn set_years(&mut self, years: i32) {
self.surpasses_years(years);
}
fn surpasses_months(&mut self, months: i32) -> bool {
let months_added =
ArithmeticDate::<C>::new_balanced(self.y0, months + i32::from(self.m0), 1, self.cal);
ArithmeticDate::<C>::compare_surpasses_ordinal(
self.sign,
months_added.year,
months_added.ordinal_month,
self.parts.day(),
self.cal_date_2,
)
}
fn set_months(&mut self, months: i32) {
let months_added =
ArithmeticDate::<C>::new_balanced(self.y0, months + i32::from(self.m0), 1, self.cal);
self.end_of_month = ArithmeticDate::<C>::new_balanced(
months_added.year,
i32::from(months_added.ordinal_month) + 1,
0,
self.cal,
);
let base_day = self.parts.day();
self.regulated_day = if base_day < self.end_of_month.day {
base_day
} else {
self.end_of_month.day
};
}
fn surpasses_weeks(&mut self, weeks: i32) -> bool {
if weeks == 0 {
return false;
}
let balanced_date = ArithmeticDate::<C>::new_balanced(
self.end_of_month.year,
i32::from(self.end_of_month.ordinal_month),
7 * weeks + i32::from(self.regulated_day),
self.cal,
);
self.weeks = weeks;
ArithmeticDate::<C>::compare_surpasses_ordinal(
self.sign,
balanced_date.year,
balanced_date.ordinal_month,
balanced_date.day,
self.cal_date_2,
)
}
#[inline]
fn set_weeks(&mut self, weeks: i32) {
self.surpasses_weeks(weeks);
}
fn surpasses_days(&mut self, days: i32) -> bool {
if self.weeks == 0 && days == 0 {
return false;
}
let balanced_date = ArithmeticDate::<C>::new_balanced(
self.end_of_month.year,
i32::from(self.end_of_month.ordinal_month),
7 * self.weeks + days + i32::from(self.regulated_day),
self.cal,
);
ArithmeticDate::<C>::compare_surpasses_ordinal(
self.sign,
balanced_date.year,
balanced_date.ordinal_month,
balanced_date.day,
self.cal_date_2,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cal::{coptic::CopticYear, *},
Date,
};
#[test]
fn test_ord() {
let new_coptic = |coptic_year, month, day| {
ArithmeticDate::<Coptic>::new_unchecked(
CopticYear::from_coptic_anno_martyrum_year(coptic_year),
month,
day,
)
};
let dates_in_order = [
new_coptic(-10, 1, 1),
new_coptic(-10, 1, 2),
new_coptic(-10, 2, 1),
new_coptic(-1, 1, 1),
new_coptic(-1, 1, 2),
new_coptic(-1, 2, 1),
new_coptic(0, 1, 1),
new_coptic(0, 1, 2),
new_coptic(0, 2, 1),
new_coptic(1, 1, 1),
new_coptic(1, 1, 2),
new_coptic(1, 2, 1),
new_coptic(10, 1, 1),
new_coptic(10, 1, 2),
new_coptic(10, 2, 1),
];
for (i, i_date) in dates_in_order.iter().enumerate() {
for (j, j_date) in dates_in_order.iter().enumerate() {
let result1 = i_date.cmp(j_date);
let result2 = j_date.cmp(i_date);
assert_eq!(result1.reverse(), result2);
assert_eq!(i.cmp(&j), i_date.cmp(j_date));
}
}
}
#[test]
pub fn zero() {
use crate::Date;
Date::try_new_iso(2024, 0, 1).unwrap_err();
Date::try_new_iso(2024, 1, 0).unwrap_err();
Date::try_new_iso(2024, 0, 0).unwrap_err();
}
#[test]
fn test_validity_ranges() {
#[rustfmt::skip]
let lowest_years = [
Date::from_rata_die(*VALID_RD_RANGE.start(), Buddhist).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.start(), ChineseTraditional::new()).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.start(), Coptic).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.start(), Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteAlem)).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.start(), Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteMihret)).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.start(), Gregorian).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.start(), Hebrew).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.start(), Hijri::new_umm_al_qura()).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.start(), Hijri::new_tabular(HijriTabularLeapYears::TypeII, HijriTabularEpoch::Thursday)).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.start(), Indian).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.start(), Iso).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.start(), Japanese::new()).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.start(), Julian).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.start(), KoreanTraditional::new()).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.start(), Persian).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.start(), Roc).year().extended_year(),
];
#[rustfmt::skip]
let highest_years = [
Date::from_rata_die(*VALID_RD_RANGE.end(), Buddhist).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.end(), ChineseTraditional::new()).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.end(), Coptic).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.end(), Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteAlem)).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.end(), Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteMihret)).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.end(), Gregorian).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.end(), Hebrew).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.end(), Hijri::new_umm_al_qura()).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.end(), Hijri::new_tabular(HijriTabularLeapYears::TypeII, HijriTabularEpoch::Thursday)).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.end(), Indian).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.end(), Iso).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.end(), Japanese::new()).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.end(), Julian).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.end(), KoreanTraditional::new()).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.end(), Persian).year().extended_year(),
Date::from_rata_die(*VALID_RD_RANGE.end(), Roc).year().extended_year(),
];
assert!(lowest_years
.iter()
.all(|y| y <= CONSTRUCTOR_YEAR_RANGE.start()));
assert!(highest_years
.iter()
.all(|y| y >= CONSTRUCTOR_YEAR_RANGE.end()));
assert!(-lowest_years.iter().copied().min().unwrap() < 1 << 20);
assert!(highest_years.iter().copied().max().unwrap() < 1 << 20);
}
#[test]
fn test_from_fields_consistent_years() {
let fields = DateFields {
extended_year: Some(0),
era_year: Some(0),
era: Some(b"be"),
ordinal_month: Some(1),
day: Some(1),
..Default::default()
};
let date = Date::try_from_fields(fields, Default::default(), Buddhist).unwrap();
assert_eq!(date.year().extended_year(), 0);
}
}