diff --git a/Cargo.toml b/Cargo.toml index f148c956b18..9a4ae862c84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -342,4 +342,5 @@ debug = true type_method_marked_deprecated.level = "allow" # allow deprecation in patch releases trait_method_marked_deprecated.level = "allow" # allow deprecation in patch releases type_marked_deprecated.level = "allow" # allow deprecation in patch releases +struct_field_marked_deprecated = "allow" # allow deprecation in patch releases enum_no_repr_variant_discriminant_changed = "allow" # we don't consider this breaking https://github.com/obi1kenobi/cargo-semver-checks/issues/1376 diff --git a/components/calendar/benches/date.rs b/components/calendar/benches/date.rs index 25786ac3f45..aab99cc0db2 100644 --- a/components/calendar/benches/date.rs +++ b/components/calendar/benches/date.rs @@ -152,10 +152,7 @@ fn date_benches(c: &mut Criterion) { "calendar/chinese_cached", &fxs, icu::calendar::cal::ChineseTraditional::new(), - |y, m, d, c| { - Date::try_new_from_codes(None, y, types::MonthCode::new_normal(m).unwrap(), d, c) - .unwrap() - }, + |y, m, d, c| Date::try_new_from_codes(None, y, types::Month::new(m).code(), d, c).unwrap(), ); bench_calendar( @@ -163,10 +160,7 @@ fn date_benches(c: &mut Criterion) { "calendar/dangi_cached", &fxs, icu::calendar::cal::KoreanTraditional::new(), - |y, m, d, c| { - Date::try_new_from_codes(None, y, types::MonthCode::new_normal(m).unwrap(), d, c) - .unwrap() - }, + |y, m, d, c| Date::try_new_from_codes(None, y, types::Month::new(m).code(), d, c).unwrap(), ); bench_calendar( @@ -174,10 +168,7 @@ fn date_benches(c: &mut Criterion) { "calendar/hebrew", &fxs, icu::calendar::cal::Hebrew, - |y, m, d, c| { - Date::try_new_from_codes(None, y, types::MonthCode::new_normal(m).unwrap(), d, c) - .unwrap() - }, + |y, m, d, c| Date::try_new_from_codes(None, y, types::Month::new(m).code(), d, c).unwrap(), ); bench_calendar( diff --git a/components/calendar/fuzz/fuzz_targets/common.rs b/components/calendar/fuzz/fuzz_targets/common.rs index 5818f9cee68..c2c0a2a2506 100644 --- a/components/calendar/fuzz/fuzz_targets/common.rs +++ b/components/calendar/fuzz/fuzz_targets/common.rs @@ -38,11 +38,11 @@ impl Ymd { fields.ordinal_month = Some(self.month); } MonthInterpretation::CodeNormal => { - code = MonthCode::new_normal(self.month)?; + code = Month::new(self.month).code(); fields.month_code = Some(code.0.as_bytes()); } MonthInterpretation::CodeLeap => { - code = MonthCode::new_leap(self.month)?; + code = Month::leap(self.month).code(); fields.month_code = Some(code.0.as_bytes()); } }; diff --git a/components/calendar/src/any_calendar.rs b/components/calendar/src/any_calendar.rs index 42ad4fd2a41..d8592519f23 100644 --- a/components/calendar/src/any_calendar.rs +++ b/components/calendar/src/any_calendar.rs @@ -1542,32 +1542,27 @@ impl From for AnyCalendar { #[cfg(test)] mod tests { - use tinystr::tinystr; - use types::MonthCode; - use super::*; - use crate::Ref; + use crate::{types::Month, Ref}; #[track_caller] fn single_test_roundtrip( calendar: Ref, era: Option<(&str, Option)>, year: i32, - month_code: &str, + month: Month, day: u8, ) { - let month = types::MonthCode(month_code.parse().expect("month code must parse")); - - let date = Date::try_new_from_codes(era.map(|x| x.0), year, month, day, calendar) + let date = Date::try_new_from_codes(era.map(|x| x.0), year, month.code(), day, calendar) .unwrap_or_else(|e| { panic!( - "Failed to construct date for {} with {era:?}, {year}, {month}, {day}: {e:?}", + "Failed to construct date for {} with {era:?}, {year}, {month:?}, {day}: {e:?}", calendar.debug_name(), ) }); let roundtrip_year = date.year(); - let roundtrip_month = date.month().standard_code; + let roundtrip_month = date.month().value; let roundtrip_day = date.day_of_month().0; assert_eq!( @@ -1604,7 +1599,7 @@ mod tests { let reconstructed = Date::new_from_iso(iso, calendar); assert_eq!( date, reconstructed, - "Failed to roundtrip via iso with {era:?}, {year}, {month}, {day}" + "Failed to roundtrip via iso with {era:?}, {year}, {month:?}, {day}" ) } @@ -1613,17 +1608,15 @@ mod tests { calendar: Ref, era: Option<(&str, Option)>, year: i32, - month_code: &str, + month: Month, day: u8, error: DateError, ) { - let month = types::MonthCode(month_code.parse().expect("month code must parse")); - - let date = Date::try_new_from_codes(era.map(|x| x.0), year, month, day, calendar); + let date = Date::try_new_from_codes(era.map(|x| x.0), year, month.code(), day, calendar); assert_eq!( date, Err(error), - "Construction with {era:?}, {year}, {month}, {day} did not return {error:?}" + "Construction with {era:?}, {year}, {month:?}, {day} did not return {error:?}" ) } @@ -1631,17 +1624,17 @@ mod tests { fn buddhist() { let buddhist = AnyCalendar::new(AnyCalendarKind::Buddhist); let buddhist = Ref(&buddhist); - single_test_roundtrip(buddhist, Some(("be", Some(0))), 100, "M03", 1); - single_test_roundtrip(buddhist, None, 100, "M03", 1); - single_test_roundtrip(buddhist, None, -100, "M03", 1); - single_test_roundtrip(buddhist, Some(("be", Some(0))), -100, "M03", 1); + single_test_roundtrip(buddhist, Some(("be", Some(0))), 100, Month::new(3), 1); + single_test_roundtrip(buddhist, None, 100, Month::new(3), 1); + single_test_roundtrip(buddhist, None, -100, Month::new(3), 1); + single_test_roundtrip(buddhist, Some(("be", Some(0))), -100, Month::new(3), 1); single_test_error( buddhist, Some(("be", Some(0))), 100, - "M13", + Month::new(13), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M13"))), + DateError::UnknownMonthCode(Month::new(13).code()), ); } @@ -1649,18 +1642,18 @@ mod tests { fn coptic() { let coptic = AnyCalendar::new(AnyCalendarKind::Coptic); let coptic = Ref(&coptic); - single_test_roundtrip(coptic, Some(("am", Some(0))), 100, "M03", 1); - single_test_roundtrip(coptic, None, 2000, "M03", 1); - single_test_roundtrip(coptic, None, -100, "M03", 1); - single_test_roundtrip(coptic, Some(("am", Some(0))), -99, "M03", 1); - single_test_roundtrip(coptic, Some(("am", Some(0))), 100, "M13", 1); + single_test_roundtrip(coptic, Some(("am", Some(0))), 100, Month::new(3), 1); + single_test_roundtrip(coptic, None, 2000, Month::new(3), 1); + single_test_roundtrip(coptic, None, -100, Month::new(3), 1); + single_test_roundtrip(coptic, Some(("am", Some(0))), -99, Month::new(3), 1); + single_test_roundtrip(coptic, Some(("am", Some(0))), 100, Month::new(13), 1); single_test_error( coptic, Some(("am", Some(0))), 100, - "M14", + Month::new(14), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M14"))), + DateError::UnknownMonthCode(Month::new(14).code()), ); } @@ -1668,18 +1661,18 @@ mod tests { fn ethiopian() { let ethiopian = AnyCalendar::new(AnyCalendarKind::Ethiopian); let ethiopian = Ref(ðiopian); - single_test_roundtrip(ethiopian, Some(("am", Some(1))), 100, "M03", 1); - single_test_roundtrip(ethiopian, None, 2000, "M03", 1); - single_test_roundtrip(ethiopian, None, -100, "M03", 1); - single_test_roundtrip(ethiopian, Some(("am", Some(1))), 2000, "M13", 1); - single_test_roundtrip(ethiopian, Some(("aa", Some(0))), 5400, "M03", 1); + single_test_roundtrip(ethiopian, Some(("am", Some(1))), 100, Month::new(3), 1); + single_test_roundtrip(ethiopian, None, 2000, Month::new(3), 1); + single_test_roundtrip(ethiopian, None, -100, Month::new(3), 1); + single_test_roundtrip(ethiopian, Some(("am", Some(1))), 2000, Month::new(13), 1); + single_test_roundtrip(ethiopian, Some(("aa", Some(0))), 5400, Month::new(3), 1); // Since #6910, the era range is not enforced in try_from_codes /* single_test_error( ethiopian, Some(("am", Some(0))), 0, - "M03", + Month::new(3), 1, DateError::Range { field: "year", @@ -1692,7 +1685,7 @@ mod tests { ethiopian, Some(("aa", Some(0))), 5600, - "M03", + Month::new(3), 1, DateError::Range { field: "year", @@ -1706,9 +1699,9 @@ mod tests { ethiopian, Some(("am", Some(0))), 100, - "M14", + Month::new(14), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M14"))), + DateError::UnknownMonthCode(Month::new(14).code()), ); } @@ -1716,17 +1709,29 @@ mod tests { fn ethiopian_amete_alem() { let ethiopian_amete_alem = AnyCalendar::new(AnyCalendarKind::EthiopianAmeteAlem); let ethiopian_amete_alem = Ref(ðiopian_amete_alem); - single_test_roundtrip(ethiopian_amete_alem, Some(("aa", Some(0))), 7000, "M13", 1); - single_test_roundtrip(ethiopian_amete_alem, None, 7000, "M13", 1); - single_test_roundtrip(ethiopian_amete_alem, None, -100, "M13", 1); - single_test_roundtrip(ethiopian_amete_alem, Some(("aa", Some(0))), 100, "M03", 1); + single_test_roundtrip( + ethiopian_amete_alem, + Some(("aa", Some(0))), + 7000, + Month::new(13), + 1, + ); + single_test_roundtrip(ethiopian_amete_alem, None, 7000, Month::new(13), 1); + single_test_roundtrip(ethiopian_amete_alem, None, -100, Month::new(13), 1); + single_test_roundtrip( + ethiopian_amete_alem, + Some(("aa", Some(0))), + 100, + Month::new(3), + 1, + ); single_test_error( ethiopian_amete_alem, Some(("aa", Some(0))), 100, - "M14", + Month::new(14), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M14"))), + DateError::UnknownMonthCode(Month::new(14).code()), ); } @@ -1734,17 +1739,17 @@ mod tests { fn gregorian() { let gregorian = AnyCalendar::new(AnyCalendarKind::Gregorian); let gregorian = Ref(&gregorian); - single_test_roundtrip(gregorian, Some(("ce", Some(1))), 100, "M03", 1); - single_test_roundtrip(gregorian, None, 2000, "M03", 1); - single_test_roundtrip(gregorian, None, -100, "M03", 1); - single_test_roundtrip(gregorian, Some(("bce", Some(0))), 100, "M03", 1); + single_test_roundtrip(gregorian, Some(("ce", Some(1))), 100, Month::new(3), 1); + single_test_roundtrip(gregorian, None, 2000, Month::new(3), 1); + single_test_roundtrip(gregorian, None, -100, Month::new(3), 1); + single_test_roundtrip(gregorian, Some(("bce", Some(0))), 100, Month::new(3), 1); // Since #6910, the era range is not enforced in try_from_codes /* single_test_error( gregorian, Some(("ce", Some(1))), 0, - "M03", + Month::new(3), 1, DateError::Range { field: "year", @@ -1757,7 +1762,7 @@ mod tests { gregorian, Some(("bce", Some(0))), 0, - "M03", + Month::new(3), 1, DateError::Range { field: "year", @@ -1771,9 +1776,9 @@ mod tests { gregorian, Some(("bce", Some(0))), 100, - "M13", + Month::new(13), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M13"))), + DateError::UnknownMonthCode(Month::new(13).code()), ); } @@ -1781,17 +1786,17 @@ mod tests { fn indian() { let indian = AnyCalendar::new(AnyCalendarKind::Indian); let indian = Ref(&indian); - single_test_roundtrip(indian, Some(("shaka", Some(0))), 100, "M03", 1); - single_test_roundtrip(indian, None, 2000, "M12", 1); - single_test_roundtrip(indian, None, -100, "M03", 1); - single_test_roundtrip(indian, Some(("shaka", Some(0))), 0, "M03", 1); + single_test_roundtrip(indian, Some(("shaka", Some(0))), 100, Month::new(3), 1); + single_test_roundtrip(indian, None, 2000, Month::new(12), 1); + single_test_roundtrip(indian, None, -100, Month::new(3), 1); + single_test_roundtrip(indian, Some(("shaka", Some(0))), 0, Month::new(3), 1); single_test_error( indian, Some(("shaka", Some(0))), 100, - "M13", + Month::new(13), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M13"))), + DateError::UnknownMonthCode(Month::new(13).code()), ); } @@ -1799,16 +1804,16 @@ mod tests { fn chinese_traditional() { let chinese_traditional = AnyCalendar::new(AnyCalendarKind::Chinese); let chinese_traditional = Ref(&chinese_traditional); - single_test_roundtrip(chinese_traditional, None, 400, "M02", 5); - single_test_roundtrip(chinese_traditional, None, 4660, "M07", 29); - single_test_roundtrip(chinese_traditional, None, -100, "M11", 12); + single_test_roundtrip(chinese_traditional, None, 400, Month::new(2), 5); + single_test_roundtrip(chinese_traditional, None, 4660, Month::new(7), 29); + single_test_roundtrip(chinese_traditional, None, -100, Month::new(11), 12); single_test_error( chinese_traditional, None, 4658, - "M13", + Month::new(13), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M13"))), + DateError::UnknownMonthCode(Month::new(13).code()), ); } @@ -1816,16 +1821,16 @@ mod tests { fn korean_traditional() { let korean_traditional = AnyCalendar::new(AnyCalendarKind::Dangi); let korean_traditional = Ref(&korean_traditional); - single_test_roundtrip(korean_traditional, None, 400, "M02", 5); - single_test_roundtrip(korean_traditional, None, 4660, "M08", 29); - single_test_roundtrip(korean_traditional, None, -1300, "M11", 12); + single_test_roundtrip(korean_traditional, None, 400, Month::new(2), 5); + single_test_roundtrip(korean_traditional, None, 4660, Month::new(8), 29); + single_test_roundtrip(korean_traditional, None, -1300, Month::new(11), 12); single_test_error( korean_traditional, None, 10393, - "M00L", + Month::leap(0), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M00L"))), + DateError::UnknownMonthCode(Month::leap(0).code()), ); } @@ -1833,21 +1838,21 @@ mod tests { fn japanese() { let japanese = AnyCalendar::new(AnyCalendarKind::Japanese); let japanese = Ref(&japanese); - single_test_roundtrip(japanese, Some(("reiwa", None)), 3, "M03", 1); - single_test_roundtrip(japanese, Some(("heisei", None)), 6, "M12", 1); - single_test_roundtrip(japanese, Some(("meiji", None)), 10, "M03", 1); - single_test_roundtrip(japanese, Some(("ce", None)), 1000, "M03", 1); - single_test_roundtrip(japanese, None, 1000, "M03", 1); - single_test_roundtrip(japanese, None, -100, "M03", 1); - single_test_roundtrip(japanese, None, 2024, "M03", 1); - single_test_roundtrip(japanese, Some(("bce", None)), 10, "M03", 1); + single_test_roundtrip(japanese, Some(("reiwa", None)), 3, Month::new(3), 1); + single_test_roundtrip(japanese, Some(("heisei", None)), 6, Month::new(12), 1); + single_test_roundtrip(japanese, Some(("meiji", None)), 10, Month::new(3), 1); + single_test_roundtrip(japanese, Some(("ce", None)), 1000, Month::new(3), 1); + single_test_roundtrip(japanese, None, 1000, Month::new(3), 1); + single_test_roundtrip(japanese, None, -100, Month::new(3), 1); + single_test_roundtrip(japanese, None, 2024, Month::new(3), 1); + single_test_roundtrip(japanese, Some(("bce", None)), 10, Month::new(3), 1); // Since #6910, the era range is not enforced in try_from_codes /* single_test_error( japanese, Some(("ce", None)), 0, - "M03", + Month::new(3), 1, DateError::Range { field: "year", @@ -1860,7 +1865,7 @@ mod tests { japanese, Some(("bce", Some(0))), 0, - "M03", + Month::new(3), 1, DateError::Range { field: "year", @@ -1874,9 +1879,9 @@ mod tests { japanese, Some(("reiwa", None)), 2, - "M13", + Month::new(13), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M13"))), + DateError::UnknownMonthCode(Month::new(13).code()), ); } @@ -1884,25 +1889,43 @@ mod tests { fn japanese_extended() { let japanese_extended = AnyCalendar::new(AnyCalendarKind::JapaneseExtended); let japanese_extended = Ref(&japanese_extended); - single_test_roundtrip(japanese_extended, Some(("reiwa", None)), 3, "M03", 1); - single_test_roundtrip(japanese_extended, Some(("heisei", None)), 6, "M12", 1); - single_test_roundtrip(japanese_extended, Some(("meiji", None)), 10, "M03", 1); + single_test_roundtrip( + japanese_extended, + Some(("reiwa", None)), + 3, + Month::new(3), + 1, + ); + single_test_roundtrip( + japanese_extended, + Some(("heisei", None)), + 6, + Month::new(12), + 1, + ); + single_test_roundtrip( + japanese_extended, + Some(("meiji", None)), + 10, + Month::new(3), + 1, + ); single_test_roundtrip( japanese_extended, Some(("tenpyokampo-749", None)), 1, - "M04", + Month::new(4), 20, ); - single_test_roundtrip(japanese_extended, Some(("ce", None)), 100, "M03", 1); - single_test_roundtrip(japanese_extended, Some(("bce", None)), 10, "M03", 1); + single_test_roundtrip(japanese_extended, Some(("ce", None)), 100, Month::new(3), 1); + single_test_roundtrip(japanese_extended, Some(("bce", None)), 10, Month::new(3), 1); // Since #6910, the era range is not enforced in try_from_codes /* single_test_error( japanext, Some(("ce", None)), 0, - "M03", + Month::new(3), 1, DateError::Range { field: "year", @@ -1915,7 +1938,7 @@ mod tests { japanext, Some(("bce", Some(0))), 0, - "M03", + Month::new(3), 1, DateError::Range { field: "year", @@ -1929,9 +1952,9 @@ mod tests { japanese_extended, Some(("reiwa", None)), 2, - "M13", + Month::new(13), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M13"))), + DateError::UnknownMonthCode(Month::new(13).code()), ); } @@ -1939,17 +1962,17 @@ mod tests { fn persian() { let persian = AnyCalendar::new(AnyCalendarKind::Persian); let persian = Ref(&persian); - single_test_roundtrip(persian, Some(("ap", Some(0))), 477, "M03", 1); - single_test_roundtrip(persian, None, 2083, "M07", 21); - single_test_roundtrip(persian, None, -100, "M07", 21); - single_test_roundtrip(persian, Some(("ap", Some(0))), 1600, "M12", 20); + single_test_roundtrip(persian, Some(("ap", Some(0))), 477, Month::new(3), 1); + single_test_roundtrip(persian, None, 2083, Month::new(7), 21); + single_test_roundtrip(persian, None, -100, Month::new(7), 21); + single_test_roundtrip(persian, Some(("ap", Some(0))), 1600, Month::new(12), 20); single_test_error( persian, Some(("ap", Some(0))), 100, - "M9", + Month::new(50), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M9"))), + DateError::UnknownMonthCode(Month::new(50).code()), ); } @@ -1957,17 +1980,17 @@ mod tests { fn hebrew() { let hebrew = AnyCalendar::new(AnyCalendarKind::Hebrew); let hebrew = Ref(&hebrew); - single_test_roundtrip(hebrew, Some(("am", Some(0))), 5773, "M03", 1); - single_test_roundtrip(hebrew, None, 4993, "M07", 21); - single_test_roundtrip(hebrew, None, -100, "M07", 21); - single_test_roundtrip(hebrew, Some(("am", Some(0))), 5012, "M12", 20); + single_test_roundtrip(hebrew, Some(("am", Some(0))), 5773, Month::new(3), 1); + single_test_roundtrip(hebrew, None, 4993, Month::new(7), 21); + single_test_roundtrip(hebrew, None, -100, Month::new(7), 21); + single_test_roundtrip(hebrew, Some(("am", Some(0))), 5012, Month::new(12), 20); single_test_error( hebrew, Some(("am", Some(0))), 100, - "M9", + Month::new(50), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M9"))), + DateError::UnknownMonthCode(Month::new(50).code()), ); } @@ -1975,24 +1998,36 @@ mod tests { fn roc() { let roc = AnyCalendar::new(AnyCalendarKind::Roc); let roc = Ref(&roc); - single_test_roundtrip(roc, Some(("roc", Some(1))), 10, "M05", 3); - single_test_roundtrip(roc, Some(("broc", Some(0))), 15, "M01", 10); - single_test_roundtrip(roc, None, 100, "M10", 30); - single_test_roundtrip(roc, None, -100, "M10", 30); + single_test_roundtrip(roc, Some(("roc", Some(1))), 10, Month::new(5), 3); + single_test_roundtrip(roc, Some(("broc", Some(0))), 15, Month::new(1), 10); + single_test_roundtrip(roc, None, 100, Month::new(10), 30); + single_test_roundtrip(roc, None, -100, Month::new(10), 30); let hijri_simulated: AnyCalendar = AnyCalendar::new(AnyCalendarKind::HijriSimulatedMecca); let hijri_simulated = Ref(&hijri_simulated); - single_test_roundtrip(hijri_simulated, Some(("ah", Some(0))), 477, "M03", 1); - single_test_roundtrip(hijri_simulated, None, 2083, "M07", 21); - single_test_roundtrip(hijri_simulated, None, -100, "M07", 21); - single_test_roundtrip(hijri_simulated, Some(("ah", Some(0))), 1600, "M12", 20); + single_test_roundtrip( + hijri_simulated, + Some(("ah", Some(0))), + 477, + Month::new(3), + 1, + ); + single_test_roundtrip(hijri_simulated, None, 2083, Month::new(7), 21); + single_test_roundtrip(hijri_simulated, None, -100, Month::new(7), 21); + single_test_roundtrip( + hijri_simulated, + Some(("ah", Some(0))), + 1600, + Month::new(12), + 20, + ); single_test_error( hijri_simulated, Some(("ah", Some(0))), 100, - "M9", + Month::new(50), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M9"))), + DateError::UnknownMonthCode(Month::new(50).code()), ); } @@ -2001,17 +2036,29 @@ mod tests { let hijri_tabular_friday: AnyCalendar = AnyCalendar::new(AnyCalendarKind::HijriTabularTypeIIFriday); let hijri_tabular_friday = Ref(&hijri_tabular_friday); - single_test_roundtrip(hijri_tabular_friday, Some(("ah", Some(0))), 477, "M03", 1); - single_test_roundtrip(hijri_tabular_friday, None, 2083, "M07", 21); - single_test_roundtrip(hijri_tabular_friday, None, -100, "M07", 21); - single_test_roundtrip(hijri_tabular_friday, Some(("ah", Some(0))), 1600, "M12", 20); + single_test_roundtrip( + hijri_tabular_friday, + Some(("ah", Some(0))), + 477, + Month::new(3), + 1, + ); + single_test_roundtrip(hijri_tabular_friday, None, 2083, Month::new(7), 21); + single_test_roundtrip(hijri_tabular_friday, None, -100, Month::new(7), 21); + single_test_roundtrip( + hijri_tabular_friday, + Some(("ah", Some(0))), + 1600, + Month::new(12), + 20, + ); single_test_error( hijri_tabular_friday, Some(("ah", Some(0))), 100, - "M9", + Month::new(50), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M9"))), + DateError::UnknownMonthCode(Month::new(50).code()), ); } @@ -2019,17 +2066,29 @@ mod tests { fn hijri_umm_al_qura() { let hijri_umm_al_qura: AnyCalendar = AnyCalendar::new(AnyCalendarKind::HijriUmmAlQura); let hijri_umm_al_qura = Ref(&hijri_umm_al_qura); - single_test_roundtrip(hijri_umm_al_qura, Some(("ah", Some(0))), 477, "M03", 1); - single_test_roundtrip(hijri_umm_al_qura, None, 2083, "M07", 21); - single_test_roundtrip(hijri_umm_al_qura, None, -100, "M07", 21); - single_test_roundtrip(hijri_umm_al_qura, Some(("ah", Some(0))), 1600, "M12", 20); + single_test_roundtrip( + hijri_umm_al_qura, + Some(("ah", Some(0))), + 477, + Month::new(3), + 1, + ); + single_test_roundtrip(hijri_umm_al_qura, None, 2083, Month::new(7), 21); + single_test_roundtrip(hijri_umm_al_qura, None, -100, Month::new(7), 21); + single_test_roundtrip( + hijri_umm_al_qura, + Some(("ah", Some(0))), + 1600, + Month::new(12), + 20, + ); single_test_error( hijri_umm_al_qura, Some(("ah", Some(0))), 100, - "M9", + Month::new(50), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M9"))), + DateError::UnknownMonthCode(Month::new(50).code()), ); } @@ -2038,23 +2097,29 @@ mod tests { let hijri_tabular_thursday: AnyCalendar = AnyCalendar::new(AnyCalendarKind::HijriTabularTypeIIThursday); let hijri_tabular_thursday = Ref(&hijri_tabular_thursday); - single_test_roundtrip(hijri_tabular_thursday, Some(("ah", Some(0))), 477, "M03", 1); - single_test_roundtrip(hijri_tabular_thursday, None, 2083, "M07", 21); - single_test_roundtrip(hijri_tabular_thursday, None, -100, "M07", 21); + single_test_roundtrip( + hijri_tabular_thursday, + Some(("ah", Some(0))), + 477, + Month::new(3), + 1, + ); + single_test_roundtrip(hijri_tabular_thursday, None, 2083, Month::new(7), 21); + single_test_roundtrip(hijri_tabular_thursday, None, -100, Month::new(7), 21); single_test_roundtrip( hijri_tabular_thursday, Some(("ah", Some(0))), 1600, - "M12", + Month::new(12), 20, ); single_test_error( hijri_tabular_thursday, Some(("ah", Some(0))), 100, - "M9", + Month::new(50), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M9"))), + DateError::UnknownMonthCode(Month::new(50).code()), ); } @@ -2062,17 +2127,17 @@ mod tests { fn iso() { let iso = AnyCalendar::new(AnyCalendarKind::Iso); let iso = Ref(&iso); - single_test_roundtrip(iso, Some(("default", Some(0))), 100, "M03", 1); - single_test_roundtrip(iso, None, 2000, "M03", 1); - single_test_roundtrip(iso, None, -100, "M03", 1); - single_test_roundtrip(iso, Some(("default", Some(0))), -100, "M03", 1); + single_test_roundtrip(iso, Some(("default", Some(0))), 100, Month::new(3), 1); + single_test_roundtrip(iso, None, 2000, Month::new(3), 1); + single_test_roundtrip(iso, None, -100, Month::new(3), 1); + single_test_roundtrip(iso, Some(("default", Some(0))), -100, Month::new(3), 1); single_test_error( iso, Some(("default", Some(0))), 100, - "M13", + Month::new(13), 1, - DateError::UnknownMonthCode(MonthCode(tinystr!(4, "M13"))), + DateError::UnknownMonthCode(Month::new(13).code()), ); } } diff --git a/components/calendar/src/cal/abstract_gregorian.rs b/components/calendar/src/cal/abstract_gregorian.rs index 14db870d271..bdbc588854f 100644 --- a/components/calendar/src/cal/abstract_gregorian.rs +++ b/components/calendar/src/cal/abstract_gregorian.rs @@ -87,7 +87,7 @@ impl DateFieldsResolver for AbstractGregorian { #[inline] fn reference_year_from_month_day( &self, - _month_code: types::ValidMonthCode, + _month: types::Month, _day: u8, ) -> Result { Ok(REFERENCE_YEAR) @@ -194,7 +194,7 @@ impl Calendar for AbstractGregorian { } fn month(&self, date: &Self::DateInner) -> types::MonthInfo { - types::MonthInfo::non_lunisolar(date.month()) + types::MonthInfo::new(self, date.cast()) } fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { diff --git a/components/calendar/src/cal/coptic.rs b/components/calendar/src/cal/coptic.rs index 3def92709de..0bb85ea046f 100644 --- a/components/calendar/src/cal/coptic.rs +++ b/components/calendar/src/cal/coptic.rs @@ -85,19 +85,19 @@ impl DateFieldsResolver for Coptic { #[inline] fn reference_year_from_month_day( &self, - month_code: types::ValidMonthCode, + month: types::Month, day: u8, ) -> Result { - Coptic::reference_year_from_month_day(month_code, day) + Coptic::reference_year_from_month_day(month, day) } } impl Coptic { pub(crate) fn reference_year_from_month_day( - month_code: types::ValidMonthCode, + month: types::Month, day: u8, ) -> Result { - let (ordinal_month, false) = month_code.to_tuple() else { + let (ordinal_month, false) = (month.number(), month.is_leap()) else { return Err(EcmaReferenceYearError::MonthCodeNotInCalendar); }; // December 31, 1972 occurs on 4th month, 22nd day, 1689 AM @@ -213,7 +213,7 @@ impl Calendar for Coptic { } fn month(&self, date: &Self::DateInner) -> types::MonthInfo { - types::MonthInfo::non_lunisolar(date.0.month()) + types::MonthInfo::new(self, date.0) } fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { @@ -257,7 +257,7 @@ impl Date { mod tests { use super::*; use crate::options::{DateFromFieldsOptions, MissingFieldsStrategy, Overflow}; - use crate::types::DateFields; + use crate::types::{DateFields, Month}; #[test] fn test_coptic_regression() { @@ -270,10 +270,11 @@ mod tests { #[test] fn test_from_fields_monthday_constrain() { - // M13-7 is not a real day, however this should resolve to M12-6 + // M13-7 is not a real day, however this should resolve to M13-6 // with Overflow::Constrain + let month = Month::new(13).code(); let fields = DateFields { - month_code: Some(b"M13"), + month_code: Some(month.0.as_bytes()), day: Some(7), ..Default::default() }; diff --git a/components/calendar/src/cal/east_asian_traditional.rs b/components/calendar/src/cal/east_asian_traditional.rs index ec9115cf5f8..22e83be9e11 100644 --- a/components/calendar/src/cal/east_asian_traditional.rs +++ b/components/calendar/src/cal/east_asian_traditional.rs @@ -9,7 +9,7 @@ use crate::error::{ }; use crate::options::{DateAddOptions, DateDifferenceOptions}; use crate::options::{DateFromFieldsOptions, Overflow}; -use crate::types::ValidMonthCode; +use crate::types::Month; use crate::AsCalendar; use crate::{types, Calendar, Date}; use calendrical_calculations::chinese_based; @@ -123,12 +123,7 @@ pub trait Rules: Clone + core::fmt::Debug + crate::cal::scaffold::UnstableSealed /// /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-nonisomonthdaytoisoreferencedate /// [`MissingFieldsStrategy::Ecma`]: crate::options::MissingFieldsStrategy::Ecma - fn ecma_reference_year( - &self, - // TODO: Consider accepting ValidMonthCode - _month_code: (u8, bool), - _day: u8, - ) -> Result { + fn ecma_reference_year(&self, _month: Month, _day: u8) -> Result { Err(EcmaReferenceYearError::Unimplemented) } @@ -223,14 +218,9 @@ impl Rules for China { } } - fn ecma_reference_year( - &self, - month_code: (u8, bool), - day: u8, - ) -> Result { - let (number, is_leap) = month_code; + fn ecma_reference_year(&self, month: Month, day: u8) -> Result { // Computed by `generate_reference_years` - let extended_year = match (number, is_leap, day > 29) { + let extended_year = match (month.number(), month.is_leap(), day > 29) { (1, false, false) => 1972, (1, false, true) => 1970, (1, true, false) => 1898, @@ -334,15 +324,15 @@ impl Rules for China { /// let korean_a = iso_a.to_calendar(KoreanTraditional::new()); /// let chinese_a = iso_a.to_calendar(ChineseTraditional::new()); /// -/// assert_eq!(korean_a.month().standard_code.0, "M03L"); -/// assert_eq!(chinese_a.month().standard_code.0, "M04"); +/// assert_eq!((korean_a.month().number(), korean_a.month().is_leap()), (3, true)); +/// assert_eq!((chinese_a.month().number(), chinese_a.month().is_leap()), (4, false)); /// /// let iso_b = Date::try_new_iso(2012, 5, 23).unwrap(); /// let korean_b = iso_b.to_calendar(KoreanTraditional::new()); /// let chinese_b = iso_b.to_calendar(ChineseTraditional::new()); /// -/// assert_eq!(korean_b.month().standard_code.0, "M04"); -/// assert_eq!(chinese_b.month().standard_code.0, "M04L"); +/// assert_eq!((korean_b.month().number(), korean_b.month().is_leap()), (4, false)); +/// assert_eq!((chinese_b.month().number(), chinese_b.month().is_leap()), (4, true)); /// ``` pub type KoreanTraditional = EastAsianTraditional; @@ -414,14 +404,9 @@ impl Rules for Korea { } } - fn ecma_reference_year( - &self, - month_code: (u8, bool), - day: u8, - ) -> Result { - let (number, is_leap) = month_code; + fn ecma_reference_year(&self, month: Month, day: u8) -> Result { // Computed by `generate_reference_years` - let extended_year = match (number, is_leap, day > 29) { + let extended_year = match (month.number(), month.is_leap(), day > 29) { (1, false, false) => 1972, (1, false, true) => 1970, (1, true, false) => 1898, @@ -616,18 +601,18 @@ impl DateFieldsResolver for EastAsianTraditional { #[inline] fn reference_year_from_month_day( &self, - month_code: types::ValidMonthCode, + month: types::Month, day: u8, ) -> Result { self.0 - .ecma_reference_year(month_code.to_tuple(), day) + .ecma_reference_year(month, day) .map(|y| self.0.year(y)) } - fn ordinal_month_from_code( + fn ordinal_from_month( &self, year: Self::YearInfo, - month_code: types::ValidMonthCode, + month: types::Month, options: DateFromFieldsOptions, ) -> Result { // 14 is a sentinel value, greater than all other months, for the purpose of computation only; @@ -636,11 +621,11 @@ impl DateFieldsResolver for EastAsianTraditional { // leap_month identifies the ordinal month number of the leap month, // so its month number will be leap_month - 1 - if month_code == ValidMonthCode::new_unchecked(leap_month - 1, true) { + if month == Month::leap(leap_month - 1) { return Ok(leap_month); } - let (number @ 1..13, leap) = month_code.to_tuple() else { + let (number @ 1..13, leap) = (month.number(), month.is_leap()) else { return Err(MonthCodeError::NotInCalendar); }; @@ -653,14 +638,18 @@ impl DateFieldsResolver for EastAsianTraditional { Ok(number + (number >= leap_month) as u8) } - fn month_code_from_ordinal(&self, year: Self::YearInfo, ordinal_month: u8) -> ValidMonthCode { + fn month_from_ordinal(&self, year: Self::YearInfo, ordinal_month: u8) -> Month { // 14 is a sentinel value, greater than all other months, for the purpose of computation only; // it is impossible to actually have 14 months in a year. let leap_month = year.packed.leap_month().unwrap_or(14); - ValidMonthCode::new_unchecked( + Month::new_unchecked( // subtract one if there was a leap month before ordinal_month - (ordinal_month >= leap_month) as u8, - ordinal_month == leap_month, + if ordinal_month == leap_month { + types::LeapStatus::Leap + } else { + types::LeapStatus::Normal + }, ) } } @@ -767,10 +756,7 @@ impl Calendar for EastAsianTraditional { /// leap months. For example, in a year where an intercalary month is added after the second /// month, the month codes for ordinal months 1, 2, 3, 4, 5 would be "M01", "M02", "M02L", "M03", "M04". fn month(&self, date: &Self::DateInner) -> types::MonthInfo { - types::MonthInfo::for_code_and_ordinal( - self.month_code_from_ordinal(date.0.year(), date.0.month()), - date.0.month(), - ) + types::MonthInfo::new(self, date.0) } /// The calendar-specific day-of-month represented by `date` @@ -1211,7 +1197,7 @@ mod test { struct TestCase { year: i32, ordinal_month: u8, - month_code: types::MonthCode, + month: Month, day: u8, expected: i64, } @@ -1220,7 +1206,7 @@ mod test { TestCase { year: 2023, ordinal_month: 6, - month_code: types::MonthCode::new_normal(5).unwrap(), + month: Month::new(5), day: 6, // June 23 2023 expected: 738694, @@ -1228,7 +1214,7 @@ mod test { TestCase { year: -2636, ordinal_month: 1, - month_code: types::MonthCode::new_normal(1).unwrap(), + month: Month::new(1), day: 1, expected: -963099, }, @@ -1238,7 +1224,7 @@ mod test { let date = Date::try_new_from_codes( None, case.year, - case.month_code, + case.month.code(), case.day, ChineseTraditional::new(), ) @@ -1287,7 +1273,7 @@ mod test { assert_eq!(chinese.cyclic_year().related_iso, -2636); assert_eq!(chinese.month().ordinal, 1); - assert_eq!(chinese.month().standard_code.0, "M01"); + assert_eq!(chinese.month().value, Month::new(1)); assert_eq!(chinese.day_of_month().0, 1); assert_eq!(chinese.cyclic_year().year, 1); assert_eq!(chinese.cyclic_year().related_iso, -2636); @@ -1404,136 +1390,148 @@ mod test { } #[test] - fn test_ordinal_to_month_code() { + fn test_ordinal_to_month() { #[derive(Debug)] struct TestCase { - year: i32, - month: u8, - day: u8, - expected_code: &'static str, + iso_year: i32, + iso_month: u8, + iso_day: u8, + month: Month, } let cases = [ TestCase { - year: 2023, - month: 1, - day: 9, - expected_code: "M12", + iso_year: 2023, + iso_month: 1, + iso_day: 9, + month: Month::new(12), }, TestCase { - year: 2023, - month: 2, - day: 9, - expected_code: "M01", + iso_year: 2023, + iso_month: 2, + iso_day: 9, + month: Month::new(1), }, TestCase { - year: 2023, - month: 3, - day: 9, - expected_code: "M02", + iso_year: 2023, + iso_month: 3, + iso_day: 9, + month: Month::new(2), }, TestCase { - year: 2023, - month: 4, - day: 9, - expected_code: "M02L", + iso_year: 2023, + iso_month: 4, + iso_day: 9, + month: Month::leap(2), }, TestCase { - year: 2023, - month: 5, - day: 9, - expected_code: "M03", + iso_year: 2023, + iso_month: 5, + iso_day: 9, + month: Month::new(3), }, TestCase { - year: 2023, - month: 6, - day: 9, - expected_code: "M04", + iso_year: 2023, + iso_month: 6, + iso_day: 9, + month: Month::new(4), }, TestCase { - year: 2023, - month: 7, - day: 9, - expected_code: "M05", + iso_year: 2023, + iso_month: 7, + iso_day: 9, + month: Month::new(5), }, TestCase { - year: 2023, - month: 8, - day: 9, - expected_code: "M06", + iso_year: 2023, + iso_month: 8, + iso_day: 9, + month: Month::new(6), }, TestCase { - year: 2023, - month: 9, - day: 9, - expected_code: "M07", + iso_year: 2023, + iso_month: 9, + iso_day: 9, + month: Month::new(7), }, TestCase { - year: 2023, - month: 10, - day: 9, - expected_code: "M08", + iso_year: 2023, + iso_month: 10, + iso_day: 9, + month: Month::new(8), }, TestCase { - year: 2023, - month: 11, - day: 9, - expected_code: "M09", + iso_year: 2023, + iso_month: 11, + iso_day: 9, + month: Month::new(9), }, TestCase { - year: 2023, - month: 12, - day: 9, - expected_code: "M10", + iso_year: 2023, + iso_month: 12, + iso_day: 9, + month: Month::new(10), }, TestCase { - year: 2024, - month: 1, - day: 9, - expected_code: "M11", + iso_year: 2024, + iso_month: 1, + iso_day: 9, + month: Month::new(11), }, TestCase { - year: 2024, - month: 2, - day: 9, - expected_code: "M12", + iso_year: 2024, + iso_month: 2, + iso_day: 9, + month: Month::new(12), }, TestCase { - year: 2024, - month: 2, - day: 10, - expected_code: "M01", + iso_year: 2024, + iso_month: 2, + iso_day: 10, + month: Month::new(1), }, ]; for case in cases { - let iso = Date::try_new_iso(case.year, case.month, case.day).unwrap(); + let iso = Date::try_new_iso(case.iso_year, case.iso_month, case.iso_day).unwrap(); let chinese = iso.to_calendar(ChineseTraditional::new()); - let result_code = chinese.month().standard_code.0; - let expected_code = case.expected_code.to_string(); assert_eq!( - expected_code, result_code, + chinese.month().value, + case.month, "Month codes did not match for test case: {case:?}" ); } } #[test] - fn test_month_code_to_ordinal() { + fn test_month_to_ordinal() { let cal = ChineseTraditional::new(); let reject = DateFromFieldsOptions { overflow: Some(Overflow::Reject), ..Default::default() }; let year = cal.year_info_from_extended(2023); - let leap_month = year.packed.leap_month().unwrap(); - for ordinal in 1..=13 { - let code = ValidMonthCode::new_unchecked( - ordinal - (ordinal >= leap_month) as u8, - ordinal == leap_month, - ); + for (ordinal, month) in [ + Month::new(1), + Month::new(2), + Month::leap(2), + Month::new(3), + Month::new(4), + Month::new(5), + Month::new(6), + Month::new(7), + Month::new(8), + Month::new(9), + Month::new(10), + Month::new(11), + Month::new(12), + ] + .into_iter() + .enumerate() + { + let ordinal = ordinal as u8 + 1; assert_eq!( - cal.ordinal_month_from_code(year, code, reject), + cal.ordinal_from_month(year, month, reject), Ok(ordinal), "Code to ordinal failed for year: {}, code: {ordinal}", year.related_iso @@ -1542,7 +1540,7 @@ mod test { } #[test] - fn check_invalid_month_code_to_ordinal() { + fn check_invalid_month_to_ordinal() { let cal = ChineseTraditional::new(); let reject = DateFromFieldsOptions { overflow: Some(Overflow::Reject), @@ -1550,20 +1548,14 @@ mod test { }; for year in [4659, 4660] { let year = cal.year_info_from_extended(year); - for (code, error) in [ - ( - ValidMonthCode::new_unchecked(4, true), - MonthCodeError::NotInYear, - ), - ( - ValidMonthCode::new_unchecked(13, false), - MonthCodeError::NotInCalendar, - ), + for (month, error) in [ + (Month::leap(4), MonthCodeError::NotInYear), + (Month::new(13), MonthCodeError::NotInCalendar), ] { assert_eq!( - cal.ordinal_month_from_code(year, code, reject), + cal.ordinal_from_month(year, month, reject), Err(error), - "Invalid month code failed for year: {}, code: {code:?}", + "Invalid month code failed for year: {}, code: {month:?}", year.related_iso, ); } @@ -1639,9 +1631,10 @@ mod test { #[test] fn test_from_fields_constrain() { + let month = Month::new(1).code(); let fields = DateFields { day: Some(31), - month_code: Some(b"M01"), + month_code: Some(month.0.as_bytes()), extended_year: Some(1972), ..Default::default() }; @@ -1659,16 +1652,17 @@ mod test { ); // 2022 did not have M01L, the month should be constrained back down + let month = Month::leap(1).code(); let fields = DateFields { day: Some(1), - month_code: Some(b"M01L"), + month_code: Some(month.0.as_bytes()), extended_year: Some(2022), ..Default::default() }; let date = Date::try_from_fields(fields, options, cal).unwrap(); assert_eq!( - date.month().standard_code.0, - "M01", + date.month().value, + Month::new(1), "Month was successfully constrained" ); } @@ -1701,7 +1695,7 @@ mod test { use crate::{cal::Gregorian, Date}; let mut related_iso = 1900; - let mut lunar_month = ValidMonthCode::new_unchecked(11, false); + let mut lunar_month = Month::new(11); for year in 1901..=2100 { println!("Validating year {year}..."); @@ -1741,10 +1735,11 @@ mod test { .0 .parse() .unwrap(); - lunar_month = ValidMonthCode::new_unchecked( - new_lunar_month, - new_lunar_month == lunar_month.number(), - ); + lunar_month = if new_lunar_month == lunar_month.number() { + Month::leap(new_lunar_month) + } else { + Month::new(new_lunar_month) + }; if new_lunar_month == 1 { related_iso += 1; } @@ -1756,7 +1751,7 @@ mod test { let chinese = Date::try_new_from_codes( None, related_iso, - lunar_month.to_month_code(), + lunar_month.code(), lunar_day, ChineseTraditional::new(), ) @@ -1844,21 +1839,21 @@ mod test { for (start_year, start_month, end_year, end_month, by) in [ ( reference_year_end.extended_year(), - reference_year_end.month().month_number(), + reference_year_end.month().number(), year_1900_start.extended_year(), - year_1900_start.month().month_number(), + year_1900_start.month().number(), -1, ), ( reference_year_end.extended_year(), - reference_year_end.month().month_number(), + reference_year_end.month().number(), year_2035_end.extended_year(), - year_2035_end.month().month_number(), + year_2035_end.month().number(), 1, ), ( year_1900_start.extended_year(), - year_1900_start.month().month_number(), + year_1900_start.month().number(), -10000, 1, -1, diff --git a/components/calendar/src/cal/ethiopian.rs b/components/calendar/src/cal/ethiopian.rs index b7a8fc4e401..3ff454bb532 100644 --- a/components/calendar/src/cal/ethiopian.rs +++ b/components/calendar/src/cal/ethiopian.rs @@ -118,10 +118,10 @@ impl DateFieldsResolver for Ethiopian { #[inline] fn reference_year_from_month_day( &self, - month_code: types::ValidMonthCode, + month: types::Month, day: u8, ) -> Result { - crate::cal::Coptic::reference_year_from_month_day(month_code, day) + crate::cal::Coptic::reference_year_from_month_day(month, day) } } diff --git a/components/calendar/src/cal/hebrew.rs b/components/calendar/src/cal/hebrew.rs index fe126eba7cf..7dab67e7ab4 100644 --- a/components/calendar/src/cal/hebrew.rs +++ b/components/calendar/src/cal/hebrew.rs @@ -8,7 +8,7 @@ use crate::error::{ }; use crate::options::{DateAddOptions, DateDifferenceOptions}; use crate::options::{DateFromFieldsOptions, Overflow}; -use crate::types::{DateFields, MonthInfo, ValidMonthCode}; +use crate::types::{DateFields, LeapStatus, Month, MonthInfo}; use crate::RangeError; use crate::{types, Calendar, Date}; use ::tinystr::tinystr; @@ -151,11 +151,11 @@ impl DateFieldsResolver for Hebrew { fn reference_year_from_month_day( &self, - month_code: types::ValidMonthCode, + month: types::Month, day: u8, ) -> Result { // December 31, 1972 occurs on 4th month, 26th day, 5733 AM - let hebrew_year = match month_code.to_tuple() { + let hebrew_year = match (month.number(), month.is_leap()) { (1, false) => 5733, (2, false) => match day { // There is no day 30 in 5733 (there is in 5732) @@ -183,14 +183,14 @@ impl DateFieldsResolver for Hebrew { Ok(HebrewYear::compute(hebrew_year)) } - fn ordinal_month_from_code( + fn ordinal_from_month( &self, year: Self::YearInfo, - month_code: types::ValidMonthCode, + month: types::Month, options: DateFromFieldsOptions, ) -> Result { let is_leap_year = year.keviyah.is_leap(); - let ordinal_month = match month_code.to_tuple() { + let ordinal_month = match (month.number(), month.is_leap()) { (n @ 1..=12, false) => n + (n >= 6 && is_leap_year) as u8, (5, true) => { if is_leap_year { @@ -207,15 +207,18 @@ impl DateFieldsResolver for Hebrew { Ok(ordinal_month) } - fn month_code_from_ordinal( - &self, - year: Self::YearInfo, - ordinal_month: u8, - ) -> types::ValidMonthCode { + fn month_from_ordinal(&self, year: Self::YearInfo, ordinal_month: u8) -> types::Month { let is_leap = year.keviyah.is_leap(); - ValidMonthCode::new_unchecked( + Month::new_unchecked( ordinal_month - (is_leap && ordinal_month >= 6) as u8, - ordinal_month == 6 && is_leap, + if ordinal_month == 6 && is_leap { + types::LeapStatus::Leap + } else if ordinal_month == 7 && is_leap { + // Use the leap name for Adar in a leap year + LeapStatus::FormattingLeap + } else { + LeapStatus::Normal + }, ) } } @@ -324,21 +327,7 @@ impl Calendar for Hebrew { } fn month(&self, date: &Self::DateInner) -> MonthInfo { - let valid_standard_code = self.month_code_from_ordinal(date.0.year(), date.0.month()); - - let valid_formatting_code = if valid_standard_code.number() == 6 && date.0.month() == 7 { - ValidMonthCode::new_unchecked(6, true) // M06L - } else { - valid_standard_code - }; - - types::MonthInfo { - ordinal: date.0.month(), - standard_code: valid_standard_code.to_month_code(), - valid_standard_code, - formatting_code: valid_formatting_code.to_month_code(), - valid_formatting_code, - } + MonthInfo::new(self, date.0) } fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { @@ -376,21 +365,20 @@ impl Date { mod tests { use super::*; - use crate::types::MonthCode; - - pub const TISHREI: ValidMonthCode = ValidMonthCode::new_unchecked(1, false); - pub const ḤESHVAN: ValidMonthCode = ValidMonthCode::new_unchecked(2, false); - pub const KISLEV: ValidMonthCode = ValidMonthCode::new_unchecked(3, false); - pub const TEVET: ValidMonthCode = ValidMonthCode::new_unchecked(4, false); - pub const SHEVAT: ValidMonthCode = ValidMonthCode::new_unchecked(5, false); - pub const ADARI: ValidMonthCode = ValidMonthCode::new_unchecked(5, true); - pub const ADAR: ValidMonthCode = ValidMonthCode::new_unchecked(6, false); - pub const NISAN: ValidMonthCode = ValidMonthCode::new_unchecked(7, false); - pub const IYYAR: ValidMonthCode = ValidMonthCode::new_unchecked(8, false); - pub const SIVAN: ValidMonthCode = ValidMonthCode::new_unchecked(9, false); - pub const TAMMUZ: ValidMonthCode = ValidMonthCode::new_unchecked(10, false); - pub const AV: ValidMonthCode = ValidMonthCode::new_unchecked(11, false); - pub const ELUL: ValidMonthCode = ValidMonthCode::new_unchecked(12, false); + + pub const TISHREI: Month = Month::new(1); + pub const ḤESHVAN: Month = Month::new(2); + pub const KISLEV: Month = Month::new(3); + pub const TEVET: Month = Month::new(4); + pub const SHEVAT: Month = Month::new(5); + pub const ADARI: Month = Month::leap(5); + pub const ADAR: Month = Month::new(6); + pub const NISAN: Month = Month::new(7); + pub const IYYAR: Month = Month::new(8); + pub const SIVAN: Month = Month::new(9); + pub const TAMMUZ: Month = Month::new(10); + pub const AV: Month = Month::new(11); + pub const ELUL: Month = Month::new(12); /// The leap years used in the tests below const LEAP_YEARS_IN_TESTS: [i32; 1] = [5782]; @@ -398,7 +386,7 @@ mod tests { /// are leap years please add them to LEAP_YEARS_IN_TESTS (we have this manually /// so we don't end up exercising potentially buggy codepaths to test this) #[expect(clippy::type_complexity)] - const ISO_HEBREW_DATE_PAIRS: [((i32, u8, u8), (i32, ValidMonthCode, u8)); 48] = [ + const ISO_HEBREW_DATE_PAIRS: [((i32, u8, u8), (i32, Month, u8)); 48] = [ ((2021, 1, 10), (5781, TEVET, 26)), ((2021, 1, 25), (5781, SHEVAT, 12)), ((2021, 2, 10), (5781, SHEVAT, 28)), @@ -453,7 +441,7 @@ mod tests { fn test_conversions() { for ((iso_y, iso_m, iso_d), (y, m, d)) in ISO_HEBREW_DATE_PAIRS.into_iter() { let iso_date = Date::try_new_iso(iso_y, iso_m, iso_d).unwrap(); - let hebrew_date = Date::try_new_from_codes(Some("am"), y, m.to_month_code(), d, Hebrew) + let hebrew_date = Date::try_new_from_codes(Some("am"), y, m.code(), d, Hebrew) .expect("Date should parse"); let iso_to_hebrew = iso_date.to_calendar(Hebrew); @@ -513,8 +501,8 @@ mod tests { // https://github.com/unicode-org/icu4x/issues/4893 let cal = Hebrew::new(); let era = "am"; - let month_code = MonthCode::new_normal(1).unwrap(); - let dt = Date::try_new_from_codes(Some(era), 3760, month_code, 1, cal).unwrap(); + let month = Month::new(1); + let dt = Date::try_new_from_codes(Some(era), 3760, month.code(), 1, cal).unwrap(); // Should be Saturday per: // https://www.hebcal.com/converter?hd=1&hm=Tishrei&hy=3760&h2g=1 diff --git a/components/calendar/src/cal/hijri.rs b/components/calendar/src/cal/hijri.rs index deed498d87a..6c10a1be382 100644 --- a/components/calendar/src/cal/hijri.rs +++ b/components/calendar/src/cal/hijri.rs @@ -10,6 +10,7 @@ use crate::error::{DateError, DateFromFieldsError, EcmaReferenceYearError, Unkno use crate::options::DateFromFieldsOptions; use crate::options::{DateAddOptions, DateDifferenceOptions}; use crate::types::DateFields; +use crate::types::Month; use crate::{types, Calendar, Date}; use crate::{AsCalendar, RangeError}; use calendrical_calculations::islamic::{ @@ -114,12 +115,7 @@ pub trait Rules: Clone + Debug + crate::cal::scaffold::UnstableSealed { /// /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-nonisomonthdaytoisoreferencedate /// [`MissingFieldsStrategy::Ecma`]: crate::options::MissingFieldsStrategy::Ecma - fn ecma_reference_year( - &self, - // TODO: Consider accepting ValidMonthCode - _month_code: (u8, bool), - _day: u8, - ) -> Result { + fn ecma_reference_year(&self, _month: Month, _day: u8) -> Result { Err(EcmaReferenceYearError::Unimplemented) } @@ -288,16 +284,12 @@ impl Rules for UmmAlQura { ))) } - fn ecma_reference_year( - &self, - month_code: (u8, bool), - day: u8, - ) -> Result { - let (ordinal_month, false) = month_code else { + fn ecma_reference_year(&self, month: Month, day: u8) -> Result { + if month.is_leap() { return Err(EcmaReferenceYearError::MonthCodeNotInCalendar); - }; + } - let extended_year = match (ordinal_month, day) { + let extended_year = match (month.number(), day) { (1, _) => 1392, (2, 30..) => 1390, (2, _) => 1392, @@ -377,16 +369,12 @@ impl Rules for TabularAlgorithm { }) } - fn ecma_reference_year( - &self, - month_code: (u8, bool), - day: u8, - ) -> Result { - let (ordinal_month, false) = month_code else { + fn ecma_reference_year(&self, month: Month, day: u8) -> Result { + if month.is_leap() { return Err(EcmaReferenceYearError::MonthCodeNotInCalendar); - }; + } - Ok(match (ordinal_month, day) { + Ok(match (month.number(), day) { (1, _) => 1392, (2, 30..) => 1389, (2, _) => 1392, @@ -906,11 +894,11 @@ impl DateFieldsResolver for Hijri { #[inline] fn reference_year_from_month_day( &self, - month_code: types::ValidMonthCode, + month: types::Month, day: u8, ) -> Result { self.0 - .ecma_reference_year(month_code.to_tuple(), day) + .ecma_reference_year(month, day) .map(|y| self.0.year(y)) } } @@ -1021,7 +1009,7 @@ impl Calendar for Hijri { } fn month(&self, date: &Self::DateInner) -> types::MonthInfo { - types::MonthInfo::non_lunisolar(date.0.month()) + types::MonthInfo::new(self, date.0) } fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { @@ -1092,8 +1080,6 @@ impl>> Date { #[cfg(test)] mod test { - use types::MonthCode; - use super::*; const START_YEAR: i32 = -1245; @@ -1946,7 +1932,7 @@ mod test { fn test_regression_4914() { // https://github.com/unicode-org/icu4x/issues/4914 let dt = Hijri::new_umm_al_qura() - .from_codes(Some("bh"), 6824, MonthCode::new_normal(1).unwrap(), 1) + .from_codes(Some("bh"), 6824, Month::new(1).code(), 1) .unwrap(); assert_eq!(dt.0.day(), 1); assert_eq!(dt.0.month(), 1); diff --git a/components/calendar/src/cal/indian.rs b/components/calendar/src/cal/indian.rs index bc2a09cb6b5..198fc7d073f 100644 --- a/components/calendar/src/cal/indian.rs +++ b/components/calendar/src/cal/indian.rs @@ -88,10 +88,10 @@ impl DateFieldsResolver for Indian { #[inline] fn reference_year_from_month_day( &self, - month_code: types::ValidMonthCode, + month: types::Month, day: u8, ) -> Result { - let (ordinal_month, false) = month_code.to_tuple() else { + let (ordinal_month, false) = (month.number(), month.is_leap()) else { return Err(EcmaReferenceYearError::MonthCodeNotInCalendar); }; // December 31, 1972 occurs on 10th month, 10th day, 1894 Shaka @@ -246,7 +246,7 @@ impl Calendar for Indian { } fn month(&self, date: &Self::DateInner) -> types::MonthInfo { - types::MonthInfo::non_lunisolar(date.0.month()) + types::MonthInfo::new(self, date.0) } fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { diff --git a/components/calendar/src/cal/iso.rs b/components/calendar/src/cal/iso.rs index f1df8c3f074..1d606a0f9dc 100644 --- a/components/calendar/src/cal/iso.rs +++ b/components/calendar/src/cal/iso.rs @@ -184,7 +184,7 @@ mod test { assert_eq!( ( date_from_rd.era_year().year, - date_from_rd.month().month_number(), + date_from_rd.month().number(), date_from_rd.day_of_month().0 ), (case.year, case.month, case.day), diff --git a/components/calendar/src/cal/julian.rs b/components/calendar/src/cal/julian.rs index 07b06ce468a..dc64bd872d2 100644 --- a/components/calendar/src/cal/julian.rs +++ b/components/calendar/src/cal/julian.rs @@ -111,10 +111,10 @@ impl DateFieldsResolver for Julian { #[inline] fn reference_year_from_month_day( &self, - month_code: types::ValidMonthCode, + month: types::Month, day: u8, ) -> Result { - let (ordinal_month, false) = month_code.to_tuple() else { + let (ordinal_month, false) = (month.number(), month.is_leap()) else { return Err(EcmaReferenceYearError::MonthCodeNotInCalendar); }; // December 31, 1972 occurs on 12th month, 18th day, 1972 Old Style @@ -138,10 +138,10 @@ impl Calendar for Julian { &self, era: Option<&str>, year: i32, - month_code: types::MonthCode, + month: types::MonthCode, day: u8, ) -> Result { - ArithmeticDate::from_codes(era, year, month_code, day, self).map(JulianDateInner) + ArithmeticDate::from_codes(era, year, month, day, self).map(JulianDateInner) } #[cfg(feature = "unstable")] @@ -234,7 +234,7 @@ impl Calendar for Julian { } fn month(&self, date: &Self::DateInner) -> types::MonthInfo { - types::MonthInfo::non_lunisolar(date.0.month()) + types::MonthInfo::new(self, date.0) } fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { diff --git a/components/calendar/src/cal/persian.rs b/components/calendar/src/cal/persian.rs index cbdc814230c..8db56f077ce 100644 --- a/components/calendar/src/cal/persian.rs +++ b/components/calendar/src/cal/persian.rs @@ -82,10 +82,10 @@ impl DateFieldsResolver for Persian { #[inline] fn reference_year_from_month_day( &self, - month_code: types::ValidMonthCode, + month: types::Month, day: u8, ) -> Result { - let (ordinal_month, false) = month_code.to_tuple() else { + let (ordinal_month, false) = (month.number(), month.is_leap()) else { return Err(EcmaReferenceYearError::MonthCodeNotInCalendar); }; // December 31, 1972 occurs on 10th month, 10th day, 1351 AP @@ -197,7 +197,7 @@ impl Calendar for Persian { } fn month(&self, date: &Self::DateInner) -> types::MonthInfo { - types::MonthInfo::non_lunisolar(date.0.month()) + types::MonthInfo::new(self, date.0) } fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { diff --git a/components/calendar/src/calendar_arithmetic.rs b/components/calendar/src/calendar_arithmetic.rs index 313fc43ad37..7e1345caaa0 100644 --- a/components/calendar/src/calendar_arithmetic.rs +++ b/components/calendar/src/calendar_arithmetic.rs @@ -11,7 +11,7 @@ use crate::error::{ }; use crate::options::{DateAddOptions, DateDifferenceOptions}; use crate::options::{DateFromFieldsOptions, MissingFieldsStrategy, Overflow}; -use crate::types::{DateFields, ValidMonthCode}; +use crate::types::{DateFields, Month}; use crate::{types, Calendar, DateError, RangeError}; use core::cmp::Ordering; use core::fmt::Debug; @@ -155,7 +155,7 @@ pub(crate) trait DateFieldsResolver: Calendar { /// day for the given month. fn reference_year_from_month_day( &self, - month_code: ValidMonthCode, + month: Month, day: u8, ) -> Result; @@ -170,13 +170,13 @@ pub(crate) trait DateFieldsResolver: Calendar { /// /// The default impl is for non-lunisolar calendars! #[inline] - fn ordinal_month_from_code( + fn ordinal_from_month( &self, year: Self::YearInfo, - month_code: ValidMonthCode, + month: Month, _options: DateFromFieldsOptions, ) -> Result { - match month_code.to_tuple() { + match (month.number(), month.is_leap()) { (month_number, false) if (1..=Self::months_in_provided_year(year)).contains(&month_number) => { @@ -192,8 +192,8 @@ pub(crate) trait DateFieldsResolver: Calendar { /// /// The default impl is for non-lunisolar calendars! #[inline] - fn month_code_from_ordinal(&self, _year: Self::YearInfo, ordinal_month: u8) -> ValidMonthCode { - ValidMonthCode::new_unchecked(ordinal_month, false) + fn month_from_ordinal(&self, _year: Self::YearInfo, ordinal_month: u8) -> Month { + Month::new_unchecked(ordinal_month, types::LeapStatus::Normal) } } @@ -233,12 +233,11 @@ impl ArithmeticDate { } else { calendar.year_info_from_extended(year) }; - let validated = - ValidMonthCode::try_from_utf8(month_code.0.as_bytes()).map_err(|e| match e { - MonthCodeParseError::InvalidSyntax => DateError::UnknownMonthCode(month_code), - })?; + let validated = Month::try_from_utf8(month_code.0.as_bytes()).map_err(|e| match e { + MonthCodeParseError::InvalidSyntax => DateError::UnknownMonthCode(month_code), + })?; let month = calendar - .ordinal_month_from_code(year, validated, Default::default()) + .ordinal_from_month(year, validated, Default::default()) .map_err(|e| match e { MonthCodeError::NotInCalendar | MonthCodeError::NotInYear => { DateError::UnknownMonthCode(month_code) @@ -304,7 +303,7 @@ impl ArithmeticDate { MissingFieldsStrategy::Ecma => { match (fields.month_code, fields.ordinal_month) { (Some(month_code), None) => { - let validated = ValidMonthCode::try_from_utf8(month_code)?; + let validated = Month::try_from_utf8(month_code)?; valid_month_code = Some(validated); calendar.reference_year_from_month_day(validated, day)? } @@ -337,9 +336,9 @@ impl ArithmeticDate { Some(month_code) => { let validated = match valid_month_code { Some(validated) => validated, - None => ValidMonthCode::try_from_utf8(month_code)?, + None => Month::try_from_utf8(month_code)?, }; - let computed_month = calendar.ordinal_month_from_code(year, validated, options)?; + let computed_month = calendar.ordinal_from_month(year, validated, options)?; if let Some(ordinal_month) = fields.ordinal_month { if computed_month != ordinal_month { return Err(DateFromFieldsError::InconsistentMonth); @@ -475,12 +474,12 @@ impl ArithmeticDate { // 1. Let _y0_ be _parts_.[[Year]] + _years_. let y0 = cal.year_info_from_extended(duration.add_years_to(self.year().to_extended_year())); // 1. Let _m0_ be MonthCodeToOrdinal(_calendar_, _y0_, ! ConstrainMonthCode(_calendar_, _y0_, _parts_.[[MonthCode]], ~constrain~)). - let base_month_code = cal.month_code_from_ordinal(self.year(), self.month()); + let base_month = cal.month_from_ordinal(self.year(), self.month()); let constrain = DateFromFieldsOptions { overflow: Some(Overflow::Constrain), ..Default::default() }; - let m0_result = cal.ordinal_month_from_code(y0, base_month_code, constrain); + let m0_result = cal.ordinal_from_month(y0, base_month, constrain); let m0 = match m0_result { Ok(m0) => m0, Err(_) => { @@ -573,9 +572,9 @@ impl ArithmeticDate { // 1. Let _y0_ be _parts_.[[Year]] + _duration_.[[Years]]. let y0 = cal.year_info_from_extended(duration.add_years_to(self.year().to_extended_year())); // 1. Let _m0_ be MonthCodeToOrdinal(_calendar_, _y0_, ! ConstrainMonthCode(_calendar_, _y0_, _parts_.[[MonthCode]], _overflow_)). - let base_month = cal.month_code_from_ordinal(self.year(), self.month()); + let base_month = cal.month_from_ordinal(self.year(), self.month()); let m0 = cal - .ordinal_month_from_code( + .ordinal_from_month( y0, base_month, DateFromFieldsOptions::from_add_options(options), @@ -583,12 +582,8 @@ impl ArithmeticDate { .map_err(|e| { // TODO: Use a narrower error type here. For now, convert into DateError. match e { - MonthCodeError::NotInCalendar => { - DateError::UnknownMonthCode(base_month.to_month_code()) - } - MonthCodeError::NotInYear => { - DateError::UnknownMonthCode(base_month.to_month_code()) - } + MonthCodeError::NotInCalendar => DateError::UnknownMonthCode(base_month.code()), + MonthCodeError::NotInYear => DateError::UnknownMonthCode(base_month.code()), } })?; // 1. Let _endOfMonth_ be BalanceNonISODate(_calendar_, _y0_, _m0_ + _duration_.[[Months]] + 1, 0). @@ -766,7 +761,7 @@ mod tests { #[test] fn test_validity_ranges() { - use crate::{cal::*, types::MonthCode, Date}; + use crate::{cal::*, Date}; #[rustfmt::skip] let lowest_years = [ @@ -810,42 +805,42 @@ mod tests { #[rustfmt::skip] let lowest_rds = [ - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, Buddhist).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, ChineseTraditional::new()).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, Coptic).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteAlem)).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteMihret)).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, Gregorian).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, Hebrew).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, Hijri::new_umm_al_qura()).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, Hijri::new_tabular(HijriTabularLeapYears::TypeII, HijriTabularEpoch::Thursday)).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, Indian).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, Iso).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, Japanese::new()).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, Julian).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, KoreanTraditional::new()).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, Persian).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), MonthCode::new_normal(1).unwrap(), 1, Roc).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, Buddhist).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, ChineseTraditional::new()).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, Coptic).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteAlem)).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteMihret)).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, Gregorian).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, Hebrew).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, Hijri::new_umm_al_qura()).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, Hijri::new_tabular(HijriTabularLeapYears::TypeII, HijriTabularEpoch::Thursday)).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, Indian).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, Iso).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, Japanese::new()).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, Julian).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, KoreanTraditional::new()).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, Persian).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.start(), Month::new(1).code(), 1, Roc).unwrap().to_rata_die(), ]; #[rustfmt::skip] let highest_rds = [ - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(12).unwrap(), 31, Buddhist).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(12).unwrap(), 30, ChineseTraditional::new()).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(13).unwrap(), 5, Coptic).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(13).unwrap(), 5, Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteAlem)).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(13).unwrap(), 5, Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteMihret)).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(12).unwrap(), 31, Gregorian).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(12).unwrap(), 29, Hebrew).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(12).unwrap(), 30, Hijri::new_umm_al_qura()).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(12).unwrap(), 30, Hijri::new_tabular(HijriTabularLeapYears::TypeII, HijriTabularEpoch::Thursday)).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(12).unwrap(), 30, Indian).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(12).unwrap(), 31, Iso).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(12).unwrap(), 31, Japanese::new()).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(12).unwrap(), 31, Julian).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(12).unwrap(), 30, KoreanTraditional::new()).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(12).unwrap(), 30, Persian).unwrap().to_rata_die(), - Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), MonthCode::new_normal(12).unwrap(), 31, Roc).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(12).code(), 31, Buddhist).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(12).code(), 30, ChineseTraditional::new()).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(13).code(), 5, Coptic).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(13).code(), 5, Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteAlem)).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(13).code(), 5, Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteMihret)).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(12).code(), 31, Gregorian).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(12).code(), 29, Hebrew).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(12).code(), 30, Hijri::new_umm_al_qura()).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(12).code(), 30, Hijri::new_tabular(HijriTabularLeapYears::TypeII, HijriTabularEpoch::Thursday)).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(12).code(), 30, Indian).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(12).code(), 31, Iso).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(12).code(), 31, Japanese::new()).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(12).code(), 31, Julian).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(12).code(), 30, KoreanTraditional::new()).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(12).code(), 30, Persian).unwrap().to_rata_die(), + Date::try_new_from_codes(None, *VALID_YEAR_RANGE.end(), Month::new(12).code(), 31, Roc).unwrap().to_rata_die(), ]; // RD range is tight diff --git a/components/calendar/src/error.rs b/components/calendar/src/error.rs index 437700befb8..d69bd9ee69a 100644 --- a/components/calendar/src/error.rs +++ b/components/calendar/src/error.rs @@ -34,7 +34,7 @@ pub enum DateError { /// /// ``` /// use icu::calendar::cal::Hebrew; - /// use icu::calendar::types::MonthCode; + /// use icu::calendar::types::Month; /// use icu::calendar::Date; /// use icu::calendar::DateError; /// use tinystr::tinystr; @@ -42,7 +42,7 @@ pub enum DateError { /// Date::try_new_from_codes( /// None, /// 5784, - /// MonthCode::new_leap(5).unwrap(), + /// Month::leap(5).code(), /// 1, /// Hebrew, /// ) @@ -51,7 +51,7 @@ pub enum DateError { /// let err = Date::try_new_from_codes( /// None, /// 5785, - /// MonthCode::new_leap(5).unwrap(), + /// Month::leap(5).code(), /// 1, /// Hebrew, /// ) @@ -309,9 +309,11 @@ impl From for DateFromFieldsError { } } -/// Internal narrow error type for functions that only fail on parsing month codes +/// Error for [`Month`](crate::types::Month) parsing #[derive(Debug)] -pub(crate) enum MonthCodeParseError { +#[non_exhaustive] +pub enum MonthCodeParseError { + /// Invalid syntax InvalidSyntax, } diff --git a/components/calendar/src/ixdtf.rs b/components/calendar/src/ixdtf.rs index 1fe60d9720b..5cfa675a067 100644 --- a/components/calendar/src/ixdtf.rs +++ b/components/calendar/src/ixdtf.rs @@ -69,7 +69,7 @@ impl Date { /// Date::try_from_str("2024-07-17[u-ca=hebrew]", Gregorian).unwrap_err(); /// /// assert_eq!(date.era_year().year, 2024); - /// assert_eq!(date.month().standard_code.0, "M07"); + /// assert_eq!(date.month().number(), 7); /// assert_eq!(date.day_of_month().0, 17); /// ``` pub fn try_from_str(rfc_9557_str: &str, calendar: A) -> Result { diff --git a/components/calendar/src/tests/continuity_test.rs b/components/calendar/src/tests/continuity_test.rs index ba22c7c1e51..cf133815ac4 100644 --- a/components/calendar/src/tests/continuity_test.rs +++ b/components/calendar/src/tests/continuity_test.rs @@ -67,17 +67,17 @@ fn test_buddhist_continuity() { #[test] fn test_chinese_continuity() { let cal = crate::cal::ChineseTraditional::new(); - let date = Date::try_new_from_codes(None, -10, MonthCode::new_normal(1).unwrap(), 1, cal); + let date = Date::try_new_from_codes(None, -10, Month::new(1).code(), 1, cal); check_continuity(date.unwrap(), 20); - let date = Date::try_new_from_codes(None, -300, MonthCode::new_normal(1).unwrap(), 1, cal); + let date = Date::try_new_from_codes(None, -300, Month::new(1).code(), 1, cal); check_every_250_days(date.unwrap(), 2000); - let date = Date::try_new_from_codes(None, -10000, MonthCode::new_normal(1).unwrap(), 1, cal); + let date = Date::try_new_from_codes(None, -10000, Month::new(1).code(), 1, cal); check_every_250_days(date.unwrap(), 2000); - let date = Date::try_new_from_codes(None, 1899, MonthCode::new_normal(1).unwrap(), 1, cal); + let date = Date::try_new_from_codes(None, 1899, Month::new(1).code(), 1, cal); check_continuity(date.unwrap(), 20); - let date = Date::try_new_from_codes(None, 2099, MonthCode::new_normal(1).unwrap(), 1, cal); + let date = Date::try_new_from_codes(None, 2099, Month::new(1).code(), 1, cal); check_continuity(date.unwrap(), 20); } @@ -92,15 +92,15 @@ fn test_coptic_continuity() { #[test] fn test_korean_continuity() { let cal = cal::KoreanTraditional::new(); - let date = Date::try_new_from_codes(None, -10, MonthCode::new_normal(1).unwrap(), 1, cal); + let date = Date::try_new_from_codes(None, -10, Month::new(1).code(), 1, cal); check_continuity(date.unwrap(), 20); - let date = Date::try_new_from_codes(None, -300, MonthCode::new_normal(1).unwrap(), 1, cal); + let date = Date::try_new_from_codes(None, -300, Month::new(1).code(), 1, cal); check_every_250_days(date.unwrap(), 2000); - let date = Date::try_new_from_codes(None, 1900, MonthCode::new_normal(1).unwrap(), 1, cal); + let date = Date::try_new_from_codes(None, 1900, Month::new(1).code(), 1, cal); check_continuity(date.unwrap(), 20); - let date = Date::try_new_from_codes(None, 2100, MonthCode::new_normal(1).unwrap(), 1, cal); + let date = Date::try_new_from_codes(None, 2100, Month::new(1).code(), 1, cal); check_continuity(date.unwrap(), 20); } @@ -132,16 +132,9 @@ fn test_gregorian_continuity() { #[test] fn test_hebrew_continuity() { - let date = - Date::try_new_from_codes(None, -10, MonthCode::new_normal(1).unwrap(), 1, cal::Hebrew); - check_continuity(date.unwrap(), 20); - let date = Date::try_new_from_codes( - None, - -300, - MonthCode::new_normal(1).unwrap(), - 1, - cal::Hebrew, - ); + let date = Date::try_new_from_codes(None, -10, Month::new(1).code(), 1, cal::Hebrew); + check_continuity(date.unwrap(), 20); + let date = Date::try_new_from_codes(None, -300, Month::new(1).code(), 1, cal::Hebrew); check_every_250_days(date.unwrap(), 2000); } diff --git a/components/calendar/src/types.rs b/components/calendar/src/types.rs index 82c5164c6e9..2d024c9c5eb 100644 --- a/components/calendar/src/types.rs +++ b/components/calendar/src/types.rs @@ -13,7 +13,7 @@ use zerovec::ule::AsULE; // Export the duration types from here #[cfg(feature = "unstable")] pub use crate::duration::{DateDuration, DateDurationUnit}; -use crate::error::MonthCodeParseError; +use crate::{calendar_arithmetic::ArithmeticDate, error::MonthCodeParseError}; #[cfg(feature = "unstable")] pub use unstable::DateFields; @@ -326,16 +326,7 @@ pub struct CyclicYear { pub related_iso: i32, } -/// Representation of a month in a year -/// -/// Month codes typically look like `M01`, `M02`, etc, but can handle leap months -/// (`M03L`) in lunar calendars. Solar calendars will have codes between `M01` and `M12` -/// potentially with an `M13` for epagomenal months. Check the docs for a particular calendar -/// for details on what its month codes are. -/// -/// Month codes are shared with Temporal, [see Temporal proposal][era-proposal]. -/// -/// [era-proposal]: https://tc39.es/proposal-intl-era-monthcode/ +/// String representation of a [`Month`] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[allow(clippy::exhaustive_structs)] // this is a newtype #[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))] @@ -360,27 +351,25 @@ impl MonthCode { #[deprecated(since = "2.1.0")] /// Get the month number and whether or not it is leap from the month code pub fn parsed(self) -> Option<(u8, bool)> { - ValidMonthCode::try_from_utf8(self.0.as_bytes()) + Month::try_from_utf8(self.0.as_bytes()) .ok() - .map(ValidMonthCode::to_tuple) + .map(|m| (m.number(), m.is_leap())) } - /// Construct a "normal" month code given a number ("Mxx"). - /// - /// Returns an error for months greater than 99 + /// Deprecated, use `Month::new(m).code()` + #[deprecated(since = "2.2.0", note = "use `Month::new(m).code()`")] pub fn new_normal(number: u8) -> Option { (1..=99) .contains(&number) - .then(|| ValidMonthCode::new_unchecked(number, false).to_month_code()) + .then(|| Month::new_unchecked(number, LeapStatus::Normal).code()) } - /// Construct a "leap" month code given a number ("MxxL"). - /// - /// Returns an error for months greater than 99 + /// Deprecated, use `Month::leap(m).code()` + #[deprecated(since = "2.2.0", note = "use `Month::leap(m).code()`")] pub fn new_leap(number: u8) -> Option { (1..=99) .contains(&number) - .then(|| ValidMonthCode::new_unchecked(number, true).to_month_code()) + .then(|| Month::new_unchecked(number, LeapStatus::Leap).code()) } } @@ -427,44 +416,113 @@ impl fmt::Display for MonthCode { } } -/// A [`MonthCode`] that has been parsed into its internal representation. -#[derive(Copy, Clone, Debug, PartialEq)] -pub(crate) struct ValidMonthCode { +/// Representation of a month in a year +/// +/// A month has a "number" and "leap flag". In calendars without leap months (non-lunisolar +/// calendars), the month with number n is always the nth month of the year (_ordinal month_), +/// for example the Gregorian September is `Month:new(9)` and the 9th month of the year. +/// However, in calendars with leap months (lunisolar calendars), such as the Hebrew calendar, +/// a month might repeat (leap) without affecting the number of each subsequent month (but +/// obviously affecting their _ordinal number_). For example, the Hebrew month Nisan +/// (`Month::new(7)`) might be the 7th or 8th month of the year, depending if the month +/// Adar was repeated or not. +/// +/// Check the docs for a particular calendar for details on what its months are. +/// +/// This concept of months matches the "month code" in [Temporal], and borrows its string +/// representation: +/// * `Month::new(7)` = `M07` +/// * `Month::leap(2)` = `M02L` +/// +/// [Temporal]: https://tc39.es/proposal-intl-era-monthcode/ +#[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)] +pub struct Month { /// Month number between 0 and 99 number: u8, - is_leap: bool, + leap_status: LeapStatus, +} + +#[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)] +pub(crate) enum LeapStatus { + Normal, + Leap, + FormattingLeap, } -impl ValidMonthCode { - #[inline] - pub(crate) fn try_from_utf8(bytes: &[u8]) -> Result { +impl Month { + /// Constructs a non-leap [`Month`] with with the given number. + /// + /// The input saturates at 99. + pub const fn new(number: u8) -> Self { + Self { + number: if number > 99 { 99 } else { number }, + leap_status: LeapStatus::Normal, + } + } + + /// Constructs a leap [`Month`] with with the given number. + /// + /// The input saturates at 99. + pub const fn leap(number: u8) -> Self { + Self { + number: if number > 99 { 99 } else { number }, + leap_status: LeapStatus::Leap, + } + } + + /// Creates a [`Month`] from a Temporal month code string. + /// + /// # Example + /// ```rust + /// use icu::calendar::types::Month; + /// + /// let month = Month::try_from_str("M07L").unwrap(); + /// + /// assert_eq!(month.number(), 7); + /// assert!(month.is_leap()); + /// + /// Month::try_from_str("sep").unwrap_err(); + /// ``` + pub fn try_from_str(s: &str) -> Result { + Self::try_from_utf8(s.as_bytes()) + } + + /// Creates a [`Month`] from a Temporal month code string. + /// + /// See [`Self::try_from_str()`]. + pub fn try_from_utf8(bytes: &[u8]) -> Result { match *bytes { [b'M', tens, ones] => Ok(Self { number: (tens - b'0') * 10 + ones - b'0', - is_leap: false, + leap_status: LeapStatus::Normal, }), [b'M', tens, ones, b'L'] => Ok(Self { number: (tens - b'0') * 10 + ones - b'0', - is_leap: true, + leap_status: LeapStatus::Leap, }), _ => Err(MonthCodeParseError::InvalidSyntax), } } - /// Create a new ValidMonthCode without checking that the number is between 1 and 99 - #[inline] - pub(crate) const fn new_unchecked(number: u8, is_leap: bool) -> Self { + // precondition: number <= 99 + pub(crate) const fn new_unchecked(number: u8, leap_status: LeapStatus) -> Self { debug_assert!(1 <= number && number <= 99); - Self { number, is_leap } + Self { + number, + leap_status, + } } - /// Returns the month number according to the month code. + /// Returns the month number. + /// + /// A month number N is not necessarily the Nth month in the year if there are leap + /// months in the year. There may be multiple month N in a year. /// /// This is NOT the same as the ordinal month! /// /// # Examples /// - /// ```ignore + /// ``` /// use icu::calendar::Date; /// use icu::calendar::cal::Hebrew; /// @@ -473,37 +531,55 @@ impl ValidMonthCode { /// /// // Hebrew year 5784 was a leap year, so the ordinal month and month number diverge. /// assert_eq!(month_info.ordinal, 10); - /// assert_eq!(month_info.valid_month_code.number(), 9); + /// assert_eq!(month_info.number(), 9); /// ``` - #[inline] pub fn number(self) -> u8 { self.number } /// Returns whether the month is a leap month. /// - /// This is true for intercalary months in [`Hebrew`] and [`LunarChinese`]. + /// This is true for intercalary months in [`Hebrew`] and [`EastAsianTraditional`]. /// /// [`Hebrew`]: crate::cal::Hebrew - /// [`LunarChinese`]: crate::cal::LunarChinese - #[inline] + /// [`EastAsianTraditional`]: crate::cal::east_asian_traditional::EastAsianTraditional pub fn is_leap(self) -> bool { - self.is_leap + self.leap_status == LeapStatus::Leap + } + + /// Returns whether the [`Month`] is a formatting-leap month + /// + /// This is true for months that format differently during leap years, even if they are not + /// considered leap months. + pub fn is_formatting_leap(self) -> bool { + self.leap_status == LeapStatus::Leap || self.leap_status == LeapStatus::FormattingLeap } - #[inline] - pub(crate) fn to_tuple(self) -> (u8, bool) { - (self.number, self.is_leap) + /// Returns the [`MonthCode`] for this month. + pub fn code(self) -> MonthCode { + #[allow(clippy::unwrap_used)] // by construction + MonthCode( + TinyAsciiStr::try_from_raw([ + b'M', + b'0' + self.number / 10, + b'0' + self.number % 10, + if self.is_leap() { b'L' } else { 0 }, + ]) + .unwrap(), + ) } - pub(crate) fn to_month_code(self) -> MonthCode { + /// Returns the formatting [`MonthCode`] for this month. + /// + /// See [`Self::is_formatting_leap`]. + pub fn formatting_code(self) -> MonthCode { #[allow(clippy::unwrap_used)] // by construction MonthCode( TinyAsciiStr::try_from_raw([ b'M', b'0' + self.number / 10, b'0' + self.number % 10, - if self.is_leap { b'L' } else { 0 }, + if self.is_formatting_leap() { b'L' } else { 0 }, ]) .unwrap(), ) @@ -514,79 +590,99 @@ impl ValidMonthCode { #[derive(Copy, Clone, Debug, PartialEq)] #[non_exhaustive] pub struct MonthInfo { - /// The month number in this given year. For calendars with leap months, all months after + /// The ordinal month number in this given year. For calendars with leap months, all months after /// the leap month will end up with an incremented number. /// - /// In general, prefer using the month code in generic code. + /// In general, prefer using [`Month`]s in generic code. pub ordinal: u8, - /// The month code, used to distinguish months during leap years. + /// The [`Month`], used to distinguish months during leap years. /// /// Round-trips through `Date` constructors like [`Date::try_new_from_codes`] and [`Date::try_from_fields`]. /// /// This follows [Temporal's specification](https://tc39.es/proposal-intl-era-monthcode/#table-additional-month-codes). - /// Months considered the "same" have the same code: This means that the Hebrew months "Adar" and "Adar II" ("Adar, but during a leap year") - /// are considered the same month and have the code M05. + /// Months considered the "same" are equal: This means that the Hebrew months "Adar" and "Adar II" ("Adar, but during a leap year") + /// are considered the same month, `Month::new(6)`. /// /// [`Date::try_new_from_codes`]: crate::Date::try_new_from_codes /// [`Date::try_from_fields`]: crate::Date::try_from_fields - pub standard_code: MonthCode, + pub value: Month, - /// Same as [`Self::standard_code`] but with invariants validated. - pub(crate) valid_standard_code: ValidMonthCode, + /// The [`Month::code()`] of [`Self::value`]. + #[deprecated(since = "2.2.0", note = "use `value.code()")] + pub standard_code: MonthCode, - /// A month code, useable for formatting. - /// - /// Does NOT necessarily round-trip through `Date` constructors like [`Date::try_new_from_codes`] and [`Date::try_from_fields`]. - /// - /// This may not necessarily be the canonical month code for a month in cases where a month has different - /// formatting in a leap year, for example Adar/Adar II in the Hebrew calendar in a leap year has - /// the standard code M06, but for formatting specifically the Hebrew calendar will return M06L since it is formatted - /// differently. - /// - /// [`Date::try_new_from_codes`]: crate::Date::try_new_from_codes - /// [`Date::try_from_fields`]: crate::Date::try_from_fields + /// The [`Month::formatting_code()`] of [`Self::value`]. + #[deprecated(since = "2.2.0", note = "use `value.formatting_code()")] pub formatting_code: MonthCode, - - /// Same as [`Self::formatting_code`] but with invariants validated. - pub(crate) valid_formatting_code: ValidMonthCode, } impl MonthInfo { - pub(crate) fn non_lunisolar(number: u8) -> Self { - Self::for_code_and_ordinal(ValidMonthCode::new_unchecked(number, false), number) - } - - pub(crate) fn for_code_and_ordinal(code: ValidMonthCode, ordinal: u8) -> Self { + pub(crate) fn new( + c: &C, + date: ArithmeticDate, + ) -> Self { + let ordinal = date.month(); + let value = c.month_from_ordinal(date.year(), ordinal); + #[allow(deprecated)] // field-level allows don't work at 1.83 MSRV Self { ordinal, - standard_code: code.to_month_code(), - valid_standard_code: code, - formatting_code: code.to_month_code(), - valid_formatting_code: code, + value, + #[allow(deprecated)] + standard_code: value.code(), + #[allow(deprecated)] + formatting_code: value.code(), } } - /// Gets the month number. A month number N is not necessarily the Nth month in the year - /// if there are leap months in the year, rather it is associated with the Nth month of a "regular" - /// year. There may be multiple month Ns in a year - pub fn month_number(self) -> u8 { - self.valid_standard_code.number() + /// Returns the month number of the [`Month`]. + /// + /// A month number N is not necessarily the Nth month in the year if there are leap + /// months in the year. There may be multiple month N in a year. + /// + /// This is NOT the same as the ordinal month! + /// + /// # Examples + /// + /// ``` + /// use icu::calendar::Date; + /// use icu::calendar::cal::Hebrew; + /// + /// let hebrew_date = Date::try_new_iso(2024, 7, 1).unwrap().to_calendar(Hebrew); + /// let month_info = hebrew_date.month(); + /// + /// // Hebrew year 5784 was a leap year, so the ordinal month and month number diverge. + /// assert_eq!(month_info.ordinal, 10); + /// assert_eq!(month_info.number(), 9); + /// ``` + pub fn number(self) -> u8 { + self.value.number() } - /// Get whether the month is a leap month + /// Returns whether the [`Month`] is a leap month. + /// + /// This is true for intercalary months in [`Hebrew`] and [`EastAsianTraditional`]. + /// + /// [`Hebrew`]: crate::cal::Hebrew + /// [`EastAsianTraditional`]: crate::cal::east_asian_traditional::EastAsianTraditional pub fn is_leap(self) -> bool { - self.valid_standard_code.is_leap() + self.value.is_leap() } - #[doc(hidden)] - pub fn formatting_month_number(self) -> u8 { - self.valid_formatting_code.number() + /// Returns whether the [`Month`] is a formatting-leap month + /// + /// This is true for months that should format as leap months, even if they are not + /// considered leap months. + pub fn is_formatting_leap(self) -> bool { + self.value.is_formatting_leap() } - #[doc(hidden)] - pub fn formatting_is_leap(self) -> bool { - self.valid_formatting_code.is_leap() + /// Gets the month number. A month number N is not necessarily the Nth month in the year + /// if there are leap months in the year, rather it is associated with the Nth month of a "regular" + /// year. There may be multiple month Ns in a year + #[deprecated(since = "2.2.0", note = "use `number`")] + pub fn month_number(self) -> u8 { + self.value.number() } } diff --git a/components/calendar/tests/arithmetic.rs b/components/calendar/tests/arithmetic.rs index 37c4b97ffb7..82647b1c6fa 100644 --- a/components/calendar/tests/arithmetic.rs +++ b/components/calendar/tests/arithmetic.rs @@ -7,7 +7,7 @@ use std::convert::Infallible; use icu_calendar::{ cal::Hebrew, options::{DateAddOptions, DateDifferenceOptions, Overflow}, - types::{DateDuration, DateDurationUnit, MonthCode}, + types::{DateDuration, DateDurationUnit, Month}, AsCalendar, Calendar, Date, Iso, }; @@ -143,25 +143,13 @@ fn test_arithmetic_cases() { #[test] fn test_hebrew() { - let m06z_20 = - Date::try_new_from_codes(None, 5783, MonthCode::new_normal(6).unwrap(), 20, Hebrew) - .unwrap(); - let m05l_15 = - Date::try_new_from_codes(None, 5784, MonthCode::new_leap(5).unwrap(), 15, Hebrew).unwrap(); - let m05l_30 = - Date::try_new_from_codes(None, 5784, MonthCode::new_leap(5).unwrap(), 30, Hebrew).unwrap(); - let m06a_29 = - Date::try_new_from_codes(None, 5784, MonthCode::new_normal(6).unwrap(), 29, Hebrew) - .unwrap(); - let m07a_10 = - Date::try_new_from_codes(None, 5784, MonthCode::new_normal(7).unwrap(), 10, Hebrew) - .unwrap(); - let m06b_15 = - Date::try_new_from_codes(None, 5785, MonthCode::new_normal(6).unwrap(), 15, Hebrew) - .unwrap(); - let m07b_20 = - Date::try_new_from_codes(None, 5785, MonthCode::new_normal(7).unwrap(), 20, Hebrew) - .unwrap(); + let m06z_20 = Date::try_new_from_codes(None, 5783, Month::new(6).code(), 20, Hebrew).unwrap(); + let m05l_15 = Date::try_new_from_codes(None, 5784, Month::leap(5).code(), 15, Hebrew).unwrap(); + let m05l_30 = Date::try_new_from_codes(None, 5784, Month::leap(5).code(), 30, Hebrew).unwrap(); + let m06a_29 = Date::try_new_from_codes(None, 5784, Month::new(6).code(), 29, Hebrew).unwrap(); + let m07a_10 = Date::try_new_from_codes(None, 5784, Month::new(7).code(), 10, Hebrew).unwrap(); + let m06b_15 = Date::try_new_from_codes(None, 5785, Month::new(6).code(), 15, Hebrew).unwrap(); + let m07b_20 = Date::try_new_from_codes(None, 5785, Month::new(7).code(), 20, Hebrew).unwrap(); #[rustfmt::skip] #[allow(clippy::type_complexity)] @@ -204,18 +192,17 @@ fn test_tricky_leap_months() { let mut until_options = DateDifferenceOptions::default(); until_options.largest_unit = Some(DateDurationUnit::Years); - fn hebrew_date(year: i32, month: &str, day: u8) -> Date { - Date::try_new_from_codes(None, year, MonthCode(month.parse().unwrap()), day, Hebrew) - .unwrap() + fn hebrew_date(year: i32, month: Month, day: u8) -> Date { + Date::try_new_from_codes(None, year, month.code(), day, Hebrew).unwrap() } // M06 + 1yr = M06 (common to leap) - let date0 = hebrew_date(5783, "M06", 20); + let date0 = hebrew_date(5783, Month::new(6), 20); let duration0 = DateDuration::for_years(1); let date1 = date0 .try_added_with_options(duration0, add_options) .unwrap(); - assert_eq!(date1, hebrew_date(5784, "M06", 20)); + assert_eq!(date1, hebrew_date(5784, Month::new(6), 20)); let duration0_actual = date0.try_until_with_options(&date1, until_options).unwrap(); assert_eq!(duration0_actual, duration0); @@ -224,7 +211,7 @@ fn test_tricky_leap_months() { let date2 = date1 .try_added_with_options(duration1, add_options) .unwrap(); - assert_eq!(date2, hebrew_date(5784, "M05L", 20)); + assert_eq!(date2, hebrew_date(5784, Month::leap(5), 20)); let duration1_actual = date1.try_until_with_options(&date2, until_options).unwrap(); assert_eq!(duration1_actual, duration1); @@ -237,7 +224,7 @@ fn test_tricky_leap_months() { let date3 = date2 .try_added_with_options(duration2, add_options) .unwrap(); - assert_eq!(date3, hebrew_date(5785, "M07", 20)); + assert_eq!(date3, hebrew_date(5785, Month::new(7), 20)); let duration2_actual = date2.try_until_with_options(&date3, until_options).unwrap(); assert_eq!(duration2_actual, duration2); @@ -245,7 +232,7 @@ fn test_tricky_leap_months() { let date4 = date1 .try_added_with_options(duration2, add_options) .unwrap(); - assert_eq!(date4, hebrew_date(5785, "M07", 20)); + assert_eq!(date4, hebrew_date(5785, Month::new(7), 20)); let duration2_actual = date1.try_until_with_options(&date4, until_options).unwrap(); assert_eq!(duration2_actual, duration2); } diff --git a/components/calendar/tests/exhaustive.rs b/components/calendar/tests/exhaustive.rs index b377c10fb06..0f16fef8c75 100644 --- a/components/calendar/tests/exhaustive.rs +++ b/components/calendar/tests/exhaustive.rs @@ -38,14 +38,8 @@ fn check_from_fields() { fn test(cal: C) { let cal = Ref(&cal); - let codes = (1..19) - .flat_map(|i| { - [ - types::MonthCode::new_normal(i).unwrap(), - types::MonthCode::new_leap(i).unwrap(), - ] - .into_iter() - }) + let months = (1..19) + .flat_map(|i| [types::Month::new(i).code(), types::Month::leap(i).code()].into_iter()) .collect::>(); for year in VALID_YEAR_RANGE { if year % 50000 == 0 { @@ -54,7 +48,7 @@ fn check_from_fields() { for overflow in [options::Overflow::Constrain, options::Overflow::Reject] { let mut options = options::DateFromFieldsOptions::default(); options.overflow = Some(overflow); - for mut fields in codes + for mut fields in months .iter() .map(|m| { let mut fields = types::DateFields::default(); diff --git a/components/calendar/tests/extended_year.rs b/components/calendar/tests/extended_year.rs index 262dec5c24c..a85d0418ba7 100644 --- a/components/calendar/tests/extended_year.rs +++ b/components/calendar/tests/extended_year.rs @@ -2,7 +2,7 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). -use icu_calendar::types::MonthCode; +use icu_calendar::types::Month; use icu_calendar::AnyCalendar; use icu_calendar::AnyCalendarKind; use icu_calendar::Date; @@ -35,13 +35,13 @@ static EXTENDED_EPOCHS: &[(AnyCalendarKind, i32)] = &[ #[test] fn test_extended_year() { let iso = icu_calendar::cal::Iso; - let m_01 = MonthCode::new_normal(1).unwrap(); + let m_01 = Month::new(1); for (kind, extended_epoch) in EXTENDED_EPOCHS.iter() { let calendar = Rc::new(AnyCalendar::new(*kind)); // Create the first date in the epoch year (extended_year = 0) let date_in_epoch_year = - Date::try_new_from_codes(None, 0, m_01, 1, calendar.clone()).unwrap(); + Date::try_new_from_codes(None, 0, m_01.code(), 1, calendar.clone()).unwrap(); let iso_date_in_epoch_year = date_in_epoch_year.to_calendar(iso); assert_eq!( iso_date_in_epoch_year.extended_year(), diff --git a/components/calendar/tests/reference_year.rs b/components/calendar/tests/reference_year.rs index 35293569a68..1dc6965e2db 100644 --- a/components/calendar/tests/reference_year.rs +++ b/components/calendar/tests/reference_year.rs @@ -8,7 +8,7 @@ use icu_calendar::{ cal::*, error::DateFromFieldsError, options::{DateFromFieldsOptions, MissingFieldsStrategy, Overflow}, - types::{DateFields, MonthCode}, + types::{DateFields, Month}, Calendar, Date, Ref, }; @@ -25,7 +25,7 @@ where let mut rd = Date::try_new_iso(1972, 12, 31).unwrap().to_rata_die(); for _ in 1..2000 { let date = Date::from_rata_die(rd, Ref(&cal)); - let month_day = (date.month().standard_code, date.day_of_month().0); + let month_day = (date.month().value.code(), date.day_of_month().0); let mut fields = DateFields::default(); fields.month_code = Some(month_day.0 .0.as_bytes()); fields.day = Some(month_day.1); @@ -51,10 +51,11 @@ where } let mut fields = DateFields::default(); let mc = match is_leap { - false => MonthCode::new_normal(month_number), - true => MonthCode::new_leap(month_number), - }; - fields.month_code = mc.as_ref().map(|m| m.0.as_bytes()); + false => Month::new(month_number), + true => Month::leap(month_number), + } + .code(); + fields.month_code = Some(mc.0.as_bytes()); fields.day = Some(day_number); let mut options = DateFromFieldsOptions::default(); options.overflow = Some(Overflow::Constrain); @@ -82,7 +83,7 @@ where // Test round-trip (to valid day number) assert_eq!( fields.month_code.unwrap(), - reference_date.month().standard_code.0.as_bytes(), + reference_date.month().value.code().0.as_bytes(), "{fields:?} {cal:?}" ); assert_eq!( diff --git a/components/datetime/src/format/datetime.rs b/components/datetime/src/format/datetime.rs index e76af02583c..481e3659362 100644 --- a/components/datetime/src/format/datetime.rs +++ b/components/datetime/src/format/datetime.rs @@ -235,12 +235,7 @@ where (FieldSymbol::Month(symbol), l) => { const PART: Part = parts::MONTH; input!(PART, Month, month = input.month); - match datetime_names.get_name_for_month( - symbol, - l, - month.formatting_month_number() - 1, - month.formatting_is_leap(), - ) { + match datetime_names.get_name_for_month(symbol, l, month) { Ok(MonthPlaceholderValue::PlainString(symbol)) => { w.with_part(PART, |w| w.write_str(symbol))?; Ok(()) @@ -277,11 +272,15 @@ where } Err(e) => { w.with_part(PART, |w| { - w.with_part(Part::ERROR, |w| w.write_str(&month.formatting_code.0)) + w.with_part(Part::ERROR, |w| { + w.write_str(&month.value.formatting_code().0) + }) })?; Err(match e { GetNameForMonthError::InvalidMonthCode => { - FormattedDateTimePatternError::InvalidMonthCode(month.formatting_code) + FormattedDateTimePatternError::InvalidMonthCode( + month.value.formatting_code(), + ) } GetNameForMonthError::InvalidFieldLength => { FormattedDateTimePatternError::UnsupportedLength(ErrorField(field)) diff --git a/components/datetime/src/pattern/names.rs b/components/datetime/src/pattern/names.rs index db5e875d4c0..462c24a9c37 100644 --- a/components/datetime/src/pattern/names.rs +++ b/components/datetime/src/pattern/names.rs @@ -19,7 +19,7 @@ use crate::{external_loaders::*, DateTimeFormatterPreferences}; use crate::{scaffold::*, DateTimeFormatter, DateTimeFormatterLoadError}; use core::fmt; use core::marker::PhantomData; -use icu_calendar::types::EraYear; +use icu_calendar::types::{EraYear, MonthInfo}; use icu_calendar::AnyCalendar; use icu_decimal::options::DecimalFormatterOptions; use icu_decimal::options::GroupingStrategy; @@ -3659,8 +3659,7 @@ impl RawDateTimeNamesBorrowed<'_> { &self, field_symbol: fields::Month, field_length: FieldLength, - ordinal_index: u8, - is_leap: bool, + month: MonthInfo, ) -> Result, GetNameForMonthError> { let month_name_length = MonthNameLength::from_field(field_symbol, field_length) .ok_or(GetNameForMonthError::InvalidFieldLength)?; @@ -3668,10 +3667,10 @@ impl RawDateTimeNamesBorrowed<'_> { .month_names .get_with_variables(month_name_length) .ok_or(GetNameForMonthError::NotLoaded)?; - let month_index = usize::from(ordinal_index); + let month_index = usize::from(month.number() - 1); let name = match month_names { MonthNames::Linear(linear) => { - if is_leap { + if month.is_formatting_leap() { None } else { linear.get(month_index) @@ -3679,16 +3678,17 @@ impl RawDateTimeNamesBorrowed<'_> { } MonthNames::LeapLinear(leap_linear) => { let num_months = leap_linear.len() / 2; - if is_leap { + if month.is_formatting_leap() { leap_linear.get(month_index + num_months) } else if month_index < num_months { leap_linear.get(month_index) } else { None } + .filter(|s| !s.is_empty()) } MonthNames::LeapNumeric(leap_numeric) => { - if is_leap { + if month.is_formatting_leap() { return Ok(MonthPlaceholderValue::NumericPattern(leap_numeric)); } else { return Ok(MonthPlaceholderValue::Numeric); diff --git a/components/time/src/zone/zone_name_timestamp.rs b/components/time/src/zone/zone_name_timestamp.rs index 18587c556b4..2c21a448103 100644 --- a/components/time/src/zone/zone_name_timestamp.rs +++ b/components/time/src/zone/zone_name_timestamp.rs @@ -219,7 +219,7 @@ impl serde::Serialize for ZoneNameTimestamp { if serializer.is_human_readable() { let date_time = self.to_zoned_date_time_iso(); let year = date_time.date.era_year().year; - let month = date_time.date.month().month_number(); + let month = date_time.date.month().number(); let day = date_time.date.day_of_month().0; let hour = date_time.time.hour.number(); let minute = date_time.time.minute.number(); diff --git a/ffi/capi/bindings/cpp/icu4x/CalendarError.d.hpp b/ffi/capi/bindings/cpp/icu4x/CalendarError.d.hpp index a8d207cf638..6c79b8e09cf 100644 --- a/ffi/capi/bindings/cpp/icu4x/CalendarError.d.hpp +++ b/ffi/capi/bindings/cpp/icu4x/CalendarError.d.hpp @@ -27,7 +27,7 @@ namespace capi { namespace icu4x { /** - * Additional information: [1](https://docs.rs/icu/2.1.1/icu/calendar/struct.RangeError.html), [2](https://docs.rs/icu/2.1.1/icu/calendar/enum.DateError.html) + * Additional information: [1](https://docs.rs/icu/2.1.1/icu/calendar/struct.RangeError.html), [2](https://docs.rs/icu/2.1.1/icu/calendar/enum.DateError.html), [3](https://docs.rs/icu/2.1.1/icu/calendar/error/enum.MonthCodeParseError.html) */ class CalendarError { public: diff --git a/ffi/capi/bindings/cpp/icu4x/Date.d.hpp b/ffi/capi/bindings/cpp/icu4x/Date.d.hpp index cebeb0b369a..8cdeef1a761 100644 --- a/ffi/capi/bindings/cpp/icu4x/Date.d.hpp +++ b/ffi/capi/bindings/cpp/icu4x/Date.d.hpp @@ -65,6 +65,8 @@ class Date { * An empty era code will treat the year as an extended year * * See the [Rust documentation for `try_new_from_codes`](https://docs.rs/icu/2.1.1/icu/calendar/struct.Date.html#method.try_new_from_codes) for more information. + * + * See the [Rust documentation for `try_from_str`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.try_from_str) for more information. */ inline static icu4x::diplomat::result, icu4x::CalendarError> from_codes_in_calendar(std::string_view era_code, int32_t year, std::string_view month_code, uint8_t day, const icu4x::Calendar& calendar); @@ -141,6 +143,8 @@ class Date { * Returns the month code for this date. Typically something * like "M01", "M02", but can be more complicated for lunar calendars. * + * See the [Rust documentation for `code`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.code) for more information. + * * See the [Rust documentation for `standard_code`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.MonthInfo.html#structfield.standard_code) for more information. * * Additional information: [1](https://docs.rs/icu/2.1.1/icu/calendar/struct.Date.html#method.month) @@ -152,14 +156,14 @@ class Date { /** * Returns the month number of this month. * - * See the [Rust documentation for `month_number`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.MonthInfo.html#method.month_number) for more information. + * See the [Rust documentation for `number`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.number) for more information. */ inline uint8_t month_number() const; /** * Returns whether the month is a leap month. * - * See the [Rust documentation for `is_leap`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.MonthInfo.html#method.is_leap) for more information. + * See the [Rust documentation for `is_leap`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.is_leap) for more information. */ inline bool month_is_leap() const; diff --git a/ffi/capi/bindings/dart/CalendarError.g.dart b/ffi/capi/bindings/dart/CalendarError.g.dart index 8e41c74e0c9..8c9f031e104 100644 --- a/ffi/capi/bindings/dart/CalendarError.g.dart +++ b/ffi/capi/bindings/dart/CalendarError.g.dart @@ -3,7 +3,7 @@ part of 'lib.g.dart'; -/// Additional information: [1](https://docs.rs/icu/2.1.1/icu/calendar/struct.RangeError.html), [2](https://docs.rs/icu/2.1.1/icu/calendar/enum.DateError.html) +/// Additional information: [1](https://docs.rs/icu/2.1.1/icu/calendar/struct.RangeError.html), [2](https://docs.rs/icu/2.1.1/icu/calendar/enum.DateError.html), [3](https://docs.rs/icu/2.1.1/icu/calendar/error/enum.MonthCodeParseError.html) enum CalendarError { // ignore: public_member_api_docs diff --git a/ffi/capi/bindings/dart/Date.g.dart b/ffi/capi/bindings/dart/Date.g.dart index 3ae753a9d4e..3bab51d02c7 100644 --- a/ffi/capi/bindings/dart/Date.g.dart +++ b/ffi/capi/bindings/dart/Date.g.dart @@ -62,6 +62,8 @@ final class Date implements ffi.Finalizable { /// /// See the [Rust documentation for `try_new_from_codes`](https://docs.rs/icu/2.1.1/icu/calendar/struct.Date.html#method.try_new_from_codes) for more information. /// + /// See the [Rust documentation for `try_from_str`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.try_from_str) for more information. + /// /// Throws [CalendarError] on failure. factory Date.fromCodesInCalendar(String eraCode, int year, String monthCode, int day, Calendar calendar) { final temp = _FinalizedArena(); @@ -164,6 +166,8 @@ final class Date implements ffi.Finalizable { /// Returns the month code for this date. Typically something /// like "M01", "M02", but can be more complicated for lunar calendars. /// + /// See the [Rust documentation for `code`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.code) for more information. + /// /// See the [Rust documentation for `standard_code`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.MonthInfo.html#structfield.standard_code) for more information. /// /// Additional information: [1](https://docs.rs/icu/2.1.1/icu/calendar/struct.Date.html#method.month) @@ -175,7 +179,7 @@ final class Date implements ffi.Finalizable { /// Returns the month number of this month. /// - /// See the [Rust documentation for `month_number`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.MonthInfo.html#method.month_number) for more information. + /// See the [Rust documentation for `number`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.number) for more information. int get monthNumber { final result = _icu4x_Date_month_number_mv1(_ffi); return result; @@ -183,7 +187,7 @@ final class Date implements ffi.Finalizable { /// Returns whether the month is a leap month. /// - /// See the [Rust documentation for `is_leap`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.MonthInfo.html#method.is_leap) for more information. + /// See the [Rust documentation for `is_leap`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.is_leap) for more information. bool get monthIsLeap { final result = _icu4x_Date_month_is_leap_mv1(_ffi); return result; diff --git a/ffi/capi/bindings/js/CalendarError.d.ts b/ffi/capi/bindings/js/CalendarError.d.ts index 703e14b7c2b..5eda5bb1ca1 100644 --- a/ffi/capi/bindings/js/CalendarError.d.ts +++ b/ffi/capi/bindings/js/CalendarError.d.ts @@ -4,7 +4,7 @@ import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; /** - * Additional information: [1](https://docs.rs/icu/2.1.1/icu/calendar/struct.RangeError.html), [2](https://docs.rs/icu/2.1.1/icu/calendar/enum.DateError.html) + * Additional information: [1](https://docs.rs/icu/2.1.1/icu/calendar/struct.RangeError.html), [2](https://docs.rs/icu/2.1.1/icu/calendar/enum.DateError.html), [3](https://docs.rs/icu/2.1.1/icu/calendar/error/enum.MonthCodeParseError.html) */ export class CalendarError { diff --git a/ffi/capi/bindings/js/CalendarError.mjs b/ffi/capi/bindings/js/CalendarError.mjs index 5d3175d73f6..7cd3d526fdc 100644 --- a/ffi/capi/bindings/js/CalendarError.mjs +++ b/ffi/capi/bindings/js/CalendarError.mjs @@ -5,7 +5,7 @@ import * as diplomatRuntime from "./diplomat-runtime.mjs"; /** - * Additional information: [1](https://docs.rs/icu/2.1.1/icu/calendar/struct.RangeError.html), [2](https://docs.rs/icu/2.1.1/icu/calendar/enum.DateError.html) + * Additional information: [1](https://docs.rs/icu/2.1.1/icu/calendar/struct.RangeError.html), [2](https://docs.rs/icu/2.1.1/icu/calendar/enum.DateError.html), [3](https://docs.rs/icu/2.1.1/icu/calendar/error/enum.MonthCodeParseError.html) */ export class CalendarError { #value = undefined; diff --git a/ffi/capi/bindings/js/Date.d.ts b/ffi/capi/bindings/js/Date.d.ts index dbae47806d2..e78f475b67d 100644 --- a/ffi/capi/bindings/js/Date.d.ts +++ b/ffi/capi/bindings/js/Date.d.ts @@ -48,6 +48,8 @@ export class Date { * An empty era code will treat the year as an extended year * * See the [Rust documentation for `try_new_from_codes`](https://docs.rs/icu/2.1.1/icu/calendar/struct.Date.html#method.try_new_from_codes) for more information. + * + * See the [Rust documentation for `try_from_str`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.try_from_str) for more information. */ static fromCodesInCalendar(eraCode: string, year: number, monthCode: string, day: number, calendar: Calendar): Date; @@ -124,6 +126,8 @@ export class Date { * Returns the month code for this date. Typically something * like "M01", "M02", but can be more complicated for lunar calendars. * + * See the [Rust documentation for `code`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.code) for more information. + * * See the [Rust documentation for `standard_code`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.MonthInfo.html#structfield.standard_code) for more information. * * Additional information: [1](https://docs.rs/icu/2.1.1/icu/calendar/struct.Date.html#method.month) @@ -133,14 +137,14 @@ export class Date { /** * Returns the month number of this month. * - * See the [Rust documentation for `month_number`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.MonthInfo.html#method.month_number) for more information. + * See the [Rust documentation for `number`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.number) for more information. */ get monthNumber(): number; /** * Returns whether the month is a leap month. * - * See the [Rust documentation for `is_leap`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.MonthInfo.html#method.is_leap) for more information. + * See the [Rust documentation for `is_leap`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.is_leap) for more information. */ get monthIsLeap(): boolean; diff --git a/ffi/capi/bindings/js/Date.mjs b/ffi/capi/bindings/js/Date.mjs index fd57c08f5fe..d34f22eb73b 100644 --- a/ffi/capi/bindings/js/Date.mjs +++ b/ffi/capi/bindings/js/Date.mjs @@ -109,6 +109,8 @@ export class Date { * An empty era code will treat the year as an extended year * * See the [Rust documentation for `try_new_from_codes`](https://docs.rs/icu/2.1.1/icu/calendar/struct.Date.html#method.try_new_from_codes) for more information. + * + * See the [Rust documentation for `try_from_str`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.try_from_str) for more information. */ static fromCodesInCalendar(eraCode, year, monthCode, day, calendar) { let functionCleanupArena = new diplomatRuntime.CleanupArena(); @@ -317,6 +319,8 @@ export class Date { * Returns the month code for this date. Typically something * like "M01", "M02", but can be more complicated for lunar calendars. * + * See the [Rust documentation for `code`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.code) for more information. + * * See the [Rust documentation for `standard_code`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.MonthInfo.html#structfield.standard_code) for more information. * * Additional information: [1](https://docs.rs/icu/2.1.1/icu/calendar/struct.Date.html#method.month) @@ -338,7 +342,7 @@ export class Date { /** * Returns the month number of this month. * - * See the [Rust documentation for `month_number`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.MonthInfo.html#method.month_number) for more information. + * See the [Rust documentation for `number`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.number) for more information. */ get monthNumber() { @@ -355,7 +359,7 @@ export class Date { /** * Returns whether the month is a leap month. * - * See the [Rust documentation for `is_leap`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.MonthInfo.html#method.is_leap) for more information. + * See the [Rust documentation for `is_leap`](https://docs.rs/icu/2.1.1/icu/calendar/types/struct.Month.html#method.is_leap) for more information. */ get monthIsLeap() { diff --git a/ffi/capi/src/date.rs b/ffi/capi/src/date.rs index ed67c6e8f69..b8015cdbeaf 100644 --- a/ffi/capi/src/date.rs +++ b/ffi/capi/src/date.rs @@ -19,8 +19,6 @@ pub mod ffi { use crate::unstable::errors::ffi::CalendarDateFromFieldsError; use crate::unstable::errors::ffi::{CalendarError, Rfc9557ParseError}; - use tinystr::TinyAsciiStr; - #[diplomat::enum_convert(icu_calendar::types::Weekday)] #[diplomat::rust_link(icu::calendar::types::Weekday, Enum)] #[non_exhaustive] @@ -265,6 +263,8 @@ pub mod ffi { /// /// An empty era code will treat the year as an extended year #[diplomat::rust_link(icu::calendar::Date::try_new_from_codes, FnInStruct)] + #[diplomat::rust_link(icu::calendar::types::Month::try_from_str, FnInStruct)] + #[diplomat::rust_link(icu::calendar::types::Month::try_from_utf8, FnInStruct, hidden)] #[diplomat::attr(all(supports = fallible_constructors, supports = named_constructors), named_constructor)] pub fn from_codes_in_calendar( era_code: &DiplomatStr, @@ -278,10 +278,7 @@ pub mod ffi { } else { None }; - let month = icu_calendar::types::MonthCode( - TinyAsciiStr::try_from_utf8(month_code) - .map_err(|_| CalendarError::UnknownMonthCode)?, - ); + let month = icu_calendar::types::Month::try_from_utf8(month_code)?.code(); let cal = calendar.0.clone(); Ok(Box::new(Date(icu_calendar::Date::try_new_from_codes( era, year, month, day, cal, @@ -370,30 +367,41 @@ pub mod ffi { /// Returns the month code for this date. Typically something /// like "M01", "M02", but can be more complicated for lunar calendars. + #[diplomat::rust_link(icu::calendar::types::Month::code, FnInStruct)] #[diplomat::rust_link(icu::calendar::types::MonthInfo::standard_code, StructField)] #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct, compact)] - #[diplomat::rust_link(icu::calendar::types::MonthInfo, Struct, hidden)] + #[diplomat::rust_link(icu::calendar::types::Month::formatting_code, FnInStruct, hidden)] #[diplomat::rust_link( icu::calendar::types::MonthInfo::formatting_code, StructField, hidden )] + #[diplomat::rust_link(icu::calendar::types::Month, Struct, hidden)] #[diplomat::rust_link(icu::calendar::types::MonthInfo, Struct, hidden)] #[diplomat::attr(auto, getter)] pub fn month_code(&self, write: &mut diplomat_runtime::DiplomatWrite) { - let code = self.0.month().standard_code; + let code = self.0.month().value.code(); let _infallible = write.write_str(&code.0); } /// Returns the month number of this month. - #[diplomat::rust_link(icu::calendar::types::MonthInfo::month_number, FnInStruct)] + #[diplomat::rust_link(icu::calendar::types::Month::number, FnInStruct)] + #[diplomat::rust_link(icu::calendar::types::MonthInfo::number, FnInStruct, hidden)] + #[diplomat::rust_link(icu::calendar::types::MonthInfo::month_number, FnInStruct, hidden)] #[diplomat::attr(auto, getter)] pub fn month_number(&self) -> u8 { - self.0.month().month_number() + self.0.month().number() } /// Returns whether the month is a leap month. - #[diplomat::rust_link(icu::calendar::types::MonthInfo::is_leap, FnInStruct)] + #[diplomat::rust_link(icu::calendar::types::Month::is_leap, FnInStruct)] + #[diplomat::rust_link(icu::calendar::types::MonthInfo::is_leap, FnInStruct, hidden)] + #[diplomat::rust_link(icu::calendar::types::Month::is_formatting_leap, FnInStruct, hidden)] + #[diplomat::rust_link( + icu::calendar::types::MonthInfo::is_formatting_leap, + FnInStruct, + hidden + )] #[diplomat::attr(auto, getter)] pub fn month_is_leap(&self) -> bool { self.0.month().is_leap() diff --git a/ffi/capi/src/errors.rs b/ffi/capi/src/errors.rs index 86b6e680cfd..ce94f14d500 100644 --- a/ffi/capi/src/errors.rs +++ b/ffi/capi/src/errors.rs @@ -61,6 +61,7 @@ pub mod ffi { #[repr(C)] #[diplomat::rust_link(icu::calendar::RangeError, Struct, compact)] #[diplomat::rust_link(icu::calendar::DateError, Enum, compact)] + #[diplomat::rust_link(icu::calendar::error::MonthCodeParseError, Enum, compact)] #[cfg(feature = "calendar")] #[non_exhaustive] pub enum CalendarError { @@ -184,6 +185,16 @@ impl From for CalendarError { } } +#[cfg(feature = "calendar")] +impl From for CalendarError { + fn from(value: icu_calendar::error::MonthCodeParseError) -> Self { + match value { + icu_calendar::error::MonthCodeParseError::InvalidSyntax => Self::UnknownMonthCode, + _ => Self::Unknown, + } + } +} + #[cfg(feature = "calendar")] impl From for CalendarError { fn from(e: icu_calendar::DateError) -> Self { diff --git a/ffi/capi/tests/missing_apis.txt b/ffi/capi/tests/missing_apis.txt index 3a790ce040e..1c623c4fed5 100644 --- a/ffi/capi/tests/missing_apis.txt +++ b/ffi/capi/tests/missing_apis.txt @@ -26,6 +26,8 @@ icu::calendar::types::DateDuration::for_months#FnInStruct icu::calendar::types::DateDuration::for_weeks#FnInStruct icu::calendar::types::DateDuration::for_years#FnInStruct icu::calendar::types::DateDurationUnit#Enum +icu::calendar::types::Month::leap#FnInStruct +icu::calendar::types::Month::new#FnInStruct icu::collator::CollationKeySink::finish#FnInTrait icu::collator::CollationKeySink::write#FnInTrait icu::collator::CollationKeySink::write_byte#FnInTrait diff --git a/provider/source/src/calendar/eras.rs b/provider/source/src/calendar/eras.rs index d471d0a535e..cd174df7362 100644 --- a/provider/source/src/calendar/eras.rs +++ b/provider/source/src/calendar/eras.rs @@ -7,7 +7,8 @@ use crate::cldr_serde::eras::EraData; use crate::datetime::DatagenCalendar; use crate::SourceDataProvider; use icu::calendar::provider::*; -use icu::calendar::{types::MonthCode, AnyCalendar, Date}; +use icu::calendar::types::Month; +use icu::calendar::{AnyCalendar, Date}; use icu_provider::prelude::*; use std::collections::BTreeMap; use std::collections::HashSet; @@ -106,7 +107,7 @@ impl SourceDataProvider { data.icu4x_era_index = Date::try_new_from_codes( Some(code), 1, - MonthCode::new_normal(1).unwrap(), + Month::new(1).code(), 1, icu::calendar::Ref(&calendar), ) @@ -277,15 +278,12 @@ impl crate::IterableDataProviderCached for SourceDat // We use a single data struct for both Ethiopic calendars, make sure their indices agree #[test] pub fn ethiopic_and_ethioaa_are_compatible() { - use icu::calendar::{ - cal::{Ethiopian, EthiopianEraStyle}, - types::MonthCode, - }; + use icu::calendar::cal::{Ethiopian, EthiopianEraStyle}; assert_eq!( Date::try_new_from_codes( Some("aa"), 1, - MonthCode::new_normal(1).unwrap(), + Month::new(1).code(), 1, Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteAlem) ) @@ -295,7 +293,7 @@ pub fn ethiopic_and_ethioaa_are_compatible() { Date::try_new_from_codes( Some("aa"), 1, - MonthCode::new_normal(1).unwrap(), + Month::new(1).code(), 1, Ethiopian::new_with_era_style(EthiopianEraStyle::AmeteMihret) ) @@ -392,7 +390,7 @@ fn test_calendar_eras() { Date::try_new_from_codes( Some(era), in_era.year().era().unwrap().year, - in_era.month().standard_code, + in_era.month().value.code(), in_era.day_of_month().0, cal, ),