Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions components/calendar/src/cal/chinese.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ impl Rules for Dangi {
(2, true, false) => 1947,
(2, true, true) => 1765,
(3, false, false) => 1972,
(3, false, true) => 1966,
(3, false, true) => 1968,
(3, true, false) => 1966,
(3, true, true) => 1955,
(4, false, false) => 1972,
Expand Down Expand Up @@ -378,7 +378,7 @@ impl Rules for Dangi {
(10, false, false) => 1972,
(10, false, true) => 1972,
(10, true, false) => 1984,
(10, true, true) => -4098,
(10, true, true) => -3946,
// Dec 31, 1972 is 1972-M11-26, dates after that
// are in the next year
(11, false, false) if day > 26 => 1971,
Expand All @@ -388,8 +388,8 @@ impl Rules for Dangi {
(11, true, true) => -2173,
(12, false, false) => 1971,
(12, false, true) => 1971,
(12, true, false) => 1403,
(12, true, true) => -180,
(12, true, false) => 1889,
(12, true, true) => -1182,
_ => return Err(DateError::UnknownMonthCode(month_code)),
};
Ok(self.year_data(extended))
Expand Down Expand Up @@ -2287,8 +2287,13 @@ mod test {
#[test]
#[ignore]
fn generate_reference_years() {
generate_reference_years_for(China, crate::cal::LunarChinese::new_china());
generate_reference_years_for(Dangi, crate::cal::LunarChinese::new_dangi());
}
fn generate_reference_years_for<R: Rules + Copy>(rules: R, calendar: LunarChinese<R>) {
use crate::Date;
let calendar = crate::cal::LunarChinese::new_china();

println!("Rules for {rules:?}:");
let reference_year_end = Date::from_rata_die(
crate::cal::abstract_gregorian::LAST_DAY_OF_REFERENCE_YEAR,
calendar,
Expand Down Expand Up @@ -2333,7 +2338,7 @@ mod test {
year += by;
continue;
}
let data = China.year_data(year);
let data = rules.year_data(year);
let leap_month = data.leap_month().unwrap_or(15);
let days_in_month = data.days_in_month({
if leap && month + 1 == leap_month {
Expand Down
177 changes: 149 additions & 28 deletions components/calendar/tests/reference_year.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ use icu_calendar::{
Calendar, Date, Ref,
};

fn test_reference_year_impl<C>(cal: C)
/// Test that a given calendar produces valid monthdays
///
/// `valid_md_condition`, given (month_number, is_leap, day_number), should return whether or not
/// that combination is ever possible in that calendar
fn test_reference_year_impl<C>(cal: C, valid_md_condition: impl Fn(u8, bool, u8) -> bool)
where
C: Calendar + Debug,
{
Expand Down Expand Up @@ -39,6 +43,9 @@ where
for month_number in 1..=14 {
for is_leap in [false, true] {
for day_number in 1..=32 {
if !valid_md_condition(month_number, is_leap, day_number) {
continue;
}
let mut fields = DateFields::default();
fields.month_code = match is_leap {
false => MonthCode::new_normal(month_number),
Expand All @@ -49,9 +56,12 @@ where
options.overflow = Some(Overflow::Reject);
options.missing_fields_strategy = Some(MissingFieldsStrategy::Ecma);
let Ok(reference_date) = Date::try_from_fields(fields, options, Ref(&cal)) else {
// Not every date exists in every calendar
continue;
panic!(
"Could not find any valid reference date for {}-{day_number}",
fields.month_code.unwrap()
);
};

assert_eq!(
fields.month_code.unwrap(),
reference_date.month().standard_code,
Expand All @@ -67,103 +77,214 @@ where
}
}

fn gregorian_md_condition(month_number: u8, is_leap: bool, day_number: u8) -> bool {
// No leap months
if is_leap {
return false;
}

match month_number {
1 | 3 | 5 | 7 | 8 | 10 | 12 => day_number <= 31,
2 => day_number <= 29,
4 | 6 | 9 | 11 => day_number <= 30,
_ => {
assert!(month_number > 12);
// No other months
false
}
}
}

fn chinese_md_condition(month_number: u8, _is_leap: bool, day_number: u8) -> bool {
month_number <= 12 && day_number <= 30
}

fn coptic_md_condition(month_number: u8, is_leap: bool, day_number: u8) -> bool {
// No leap months
if is_leap {
return false;
}
match month_number {
1..=12 => day_number <= 30,
13 => day_number <= 6,
_ => false,
}
}

fn hijri_md_condition(month_number: u8, is_leap: bool, day_number: u8) -> bool {
// No leap months
if is_leap {
return false;
}
month_number <= 12 && day_number <= 30
}

fn hijri_tabular_md_condition(month_number: u8, is_leap: bool, day_number: u8) -> bool {
// No leap months
if is_leap {
return false;
}

if month_number > 12 {
return false;
}

// Odd months have 30 days, even months have 29, except for M12 in a leap year
if month_number % 2 == 0 {
if month_number == 12 {
day_number <= 30
} else {
day_number <= 29
}
} else {
day_number <= 30
}
}

fn hebrew_md_condition(month_number: u8, is_leap: bool, day_number: u8) -> bool {
if is_leap {
return month_number == 5 && day_number <= 30;
}
match month_number {
1 | 2 | 3 | 5 | 7 | 9 | 11 => day_number <= 30,
// Tevet, Adar, Iyar, Tammuz, Elul
4 | 6 | 8 | 10 | 12 => day_number <= 29,
_ => {
assert!(month_number > 12);
// No other months
false
}
}
}

#[test]
fn test_reference_year_buddhist() {
test_reference_year_impl(Buddhist)
test_reference_year_impl(Buddhist, gregorian_md_condition)
}

#[test]
fn test_reference_year_chinese() {
test_reference_year_impl(LunarChinese::new_china())
test_reference_year_impl(LunarChinese::new_china(), chinese_md_condition)
}

#[test]
fn test_reference_year_coptic() {
test_reference_year_impl(Coptic)
test_reference_year_impl(Coptic, coptic_md_condition)
}

#[test]
fn test_reference_year_dangi() {
test_reference_year_impl(LunarChinese::new_dangi())
test_reference_year_impl(LunarChinese::new_dangi(), chinese_md_condition)
}

#[test]
fn test_reference_year_ethiopian() {
test_reference_year_impl(Ethiopian::new())
test_reference_year_impl(Ethiopian::new(), coptic_md_condition)
}

#[test]
fn test_reference_year_ethiopian_amete_alem() {
test_reference_year_impl(Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteAlem))
test_reference_year_impl(
Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteAlem),
coptic_md_condition,
)
}

#[test]
fn test_reference_year_gregorian() {
test_reference_year_impl(Gregorian)
test_reference_year_impl(Gregorian, gregorian_md_condition)
}

#[test]
fn test_reference_year_julian() {
test_reference_year_impl(Julian)
test_reference_year_impl(Julian, gregorian_md_condition)
}

#[test]
fn test_reference_year_hebrew() {
test_reference_year_impl(Hebrew)
test_reference_year_impl(Hebrew, hebrew_md_condition)
}

#[test]
fn test_reference_year_indian() {
test_reference_year_impl(Indian)
test_reference_year_impl(Indian, |month_number, is_leap, day_number| {
if is_leap {
// No leap months
return false;
}
// First half of the year has long months, second half short
if month_number <= 6 {
day_number <= 31
} else if month_number <= 12 {
day_number <= 30
} else {
// No larger months
false
}
})
}

#[test]
fn test_reference_year_hijri_tabular_type_ii_friday() {
test_reference_year_impl(Hijri::new_tabular(
HijriTabularLeapYears::TypeII,
HijriTabularEpoch::Friday,
))
test_reference_year_impl(
Hijri::new_tabular(HijriTabularLeapYears::TypeII, HijriTabularEpoch::Friday),
hijri_tabular_md_condition,
)
}

#[test]
fn test_reference_year_hijri_simulated_mecca() {
test_reference_year_impl(Hijri::new_simulated_mecca())
test_reference_year_impl(Hijri::new_simulated_mecca(), hijri_md_condition)
}

#[test]
fn test_reference_year_hijri_tabular_type_ii_thursday() {
test_reference_year_impl(Hijri::new_tabular(
HijriTabularLeapYears::TypeII,
HijriTabularEpoch::Thursday,
))
test_reference_year_impl(
Hijri::new_tabular(HijriTabularLeapYears::TypeII, HijriTabularEpoch::Thursday),
hijri_tabular_md_condition,
)
}

#[test]
fn test_reference_year_hijri_umm_al_qura() {
test_reference_year_impl(Hijri::new_umm_al_qura())
test_reference_year_impl(Hijri::new_umm_al_qura(), hijri_md_condition)
}

#[test]
fn test_reference_year_iso() {
test_reference_year_impl(Iso)
test_reference_year_impl(Iso, gregorian_md_condition)
}

#[test]
fn test_reference_year_japanese() {
test_reference_year_impl(Japanese::new())
test_reference_year_impl(Japanese::new(), gregorian_md_condition)
}

#[test]
fn test_reference_year_japanese_extended() {
test_reference_year_impl(JapaneseExtended::new())
test_reference_year_impl(JapaneseExtended::new(), gregorian_md_condition)
}

#[test]
fn test_reference_year_persian() {
test_reference_year_impl(Persian)
test_reference_year_impl(Persian, |month_number, is_leap, day_number| {
if is_leap {
// No leap months
return false;
}
// First half of the year has long months, second half short
if month_number <= 6 {
day_number <= 31
} else if month_number <= 12 {
day_number <= 30
} else {
// No larger months
false
}
})
}

#[test]
fn test_reference_year_roc() {
test_reference_year_impl(Roc)
test_reference_year_impl(Roc, gregorian_md_condition)
}
Loading