diff --git a/components/calendar/src/any_calendar.rs b/components/calendar/src/any_calendar.rs index d8592519f23..79481fa214c 100644 --- a/components/calendar/src/any_calendar.rs +++ b/components/calendar/src/any_calendar.rs @@ -1634,7 +1634,7 @@ mod tests { 100, Month::new(13), 1, - DateError::UnknownMonthCode(Month::new(13).code()), + DateError::MonthNotInCalendar, ); } @@ -1653,7 +1653,7 @@ mod tests { 100, Month::new(14), 1, - DateError::UnknownMonthCode(Month::new(14).code()), + DateError::MonthNotInCalendar, ); } @@ -1701,7 +1701,7 @@ mod tests { 100, Month::new(14), 1, - DateError::UnknownMonthCode(Month::new(14).code()), + DateError::MonthNotInCalendar, ); } @@ -1731,7 +1731,7 @@ mod tests { 100, Month::new(14), 1, - DateError::UnknownMonthCode(Month::new(14).code()), + DateError::MonthNotInCalendar, ); } @@ -1778,7 +1778,7 @@ mod tests { 100, Month::new(13), 1, - DateError::UnknownMonthCode(Month::new(13).code()), + DateError::MonthNotInCalendar, ); } @@ -1796,7 +1796,7 @@ mod tests { 100, Month::new(13), 1, - DateError::UnknownMonthCode(Month::new(13).code()), + DateError::MonthNotInCalendar, ); } @@ -1813,7 +1813,7 @@ mod tests { 4658, Month::new(13), 1, - DateError::UnknownMonthCode(Month::new(13).code()), + DateError::MonthNotInCalendar, ); } @@ -1830,7 +1830,7 @@ mod tests { 10393, Month::leap(0), 1, - DateError::UnknownMonthCode(Month::leap(0).code()), + DateError::MonthNotInCalendar, ); } @@ -1881,7 +1881,7 @@ mod tests { 2, Month::new(13), 1, - DateError::UnknownMonthCode(Month::new(13).code()), + DateError::MonthNotInCalendar, ); } @@ -1954,7 +1954,7 @@ mod tests { 2, Month::new(13), 1, - DateError::UnknownMonthCode(Month::new(13).code()), + DateError::MonthNotInCalendar, ); } @@ -1972,7 +1972,7 @@ mod tests { 100, Month::new(50), 1, - DateError::UnknownMonthCode(Month::new(50).code()), + DateError::MonthNotInCalendar, ); } @@ -1990,7 +1990,7 @@ mod tests { 100, Month::new(50), 1, - DateError::UnknownMonthCode(Month::new(50).code()), + DateError::MonthNotInCalendar, ); } @@ -2027,7 +2027,7 @@ mod tests { 100, Month::new(50), 1, - DateError::UnknownMonthCode(Month::new(50).code()), + DateError::MonthNotInCalendar, ); } @@ -2058,7 +2058,7 @@ mod tests { 100, Month::new(50), 1, - DateError::UnknownMonthCode(Month::new(50).code()), + DateError::MonthNotInCalendar, ); } @@ -2088,7 +2088,7 @@ mod tests { 100, Month::new(50), 1, - DateError::UnknownMonthCode(Month::new(50).code()), + DateError::MonthNotInCalendar, ); } @@ -2119,7 +2119,7 @@ mod tests { 100, Month::new(50), 1, - DateError::UnknownMonthCode(Month::new(50).code()), + DateError::MonthNotInCalendar, ); } @@ -2137,7 +2137,7 @@ mod tests { 100, Month::new(13), 1, - DateError::UnknownMonthCode(Month::new(13).code()), + DateError::MonthNotInCalendar, ); } } diff --git a/components/calendar/src/calendar_arithmetic.rs b/components/calendar/src/calendar_arithmetic.rs index 7e1345caaa0..7cb25b3d12a 100644 --- a/components/calendar/src/calendar_arithmetic.rs +++ b/components/calendar/src/calendar_arithmetic.rs @@ -233,16 +233,10 @@ impl ArithmeticDate { } else { calendar.year_info_from_extended(year) }; - let validated = Month::try_from_utf8(month_code.0.as_bytes()).map_err(|e| match e { + let month_code = Month::try_from_utf8(month_code.0.as_bytes()).map_err(|e| match e { MonthCodeParseError::InvalidSyntax => DateError::UnknownMonthCode(month_code), })?; - let month = calendar - .ordinal_from_month(year, validated, Default::default()) - .map_err(|e| match e { - MonthCodeError::NotInCalendar | MonthCodeError::NotInYear => { - DateError::UnknownMonthCode(month_code) - } - })?; + let month = calendar.ordinal_from_month(year, month_code, Default::default())?; let day = range_check(day, "day", 1..=C::days_in_provided_month(year, month))?; @@ -579,13 +573,8 @@ impl ArithmeticDate { base_month, DateFromFieldsOptions::from_add_options(options), ) - .map_err(|e| { - // TODO: Use a narrower error type here. For now, convert into DateError. - match e { - MonthCodeError::NotInCalendar => DateError::UnknownMonthCode(base_month.code()), - MonthCodeError::NotInYear => DateError::UnknownMonthCode(base_month.code()), - } - })?; + // TODO: Use a narrower error type here. For now, convert into DateError. + .map_err(DateError::from)?; // 1. Let _endOfMonth_ be BalanceNonISODate(_calendar_, _y0_, _m0_ + _duration_.[[Months]] + 1, 0). let end_of_month = Self::new_balanced(y0, duration.add_months_to(m0) + 1, 0, cal); // 1. Let _baseDay_ be _parts_.[[Day]]. diff --git a/components/calendar/src/error.rs b/components/calendar/src/error.rs index d69bd9ee69a..e830cffb013 100644 --- a/components/calendar/src/error.rs +++ b/components/calendar/src/error.rs @@ -14,6 +14,20 @@ use displaydoc::Display; #[non_exhaustive] pub enum DateError { /// A field is out of range for its domain. + /// + /// # Examples + /// + /// ``` + /// use icu::calendar::Date; + /// use icu::calendar::error::DateError; + /// use icu::calendar::Iso; + /// use icu::calendar::types::MonthCode; + /// + /// let err = Date::try_new_from_codes(None, 2000, MonthCode::new_normal(2).unwrap(), 30, Iso) + /// .expect_err("no Feb 30 in the ISO calendar"); + /// + /// assert!(matches!(err, DateError::Range { field: "day", .. })); + /// ``` #[displaydoc("The {field} = {value} argument is out of range {min}..={max}")] Range { /// The field that is out of range, such as "year" @@ -26,41 +40,60 @@ pub enum DateError { max: i32, }, /// The era code is invalid for the calendar. - #[displaydoc("Unknown era")] + #[displaydoc("Unknown era or invalid syntax")] UnknownEra, - /// The month code is invalid for the calendar or year. + /// The month code syntax is invalid. /// /// # Examples /// /// ``` + /// use icu::calendar::error::DateError; + /// use icu::calendar::types::MonthCode; + /// use icu::calendar::Date; + /// use icu::calendar::Iso; + /// + /// let err = Date::try_new_from_codes(None, 2000, MonthCode("Jan".parse().unwrap()), 1, Iso) + /// .expect_err("month code is invalid"); + /// + /// assert!(matches!(err, DateError::UnknownMonthCode(..))); + /// ``` + #[displaydoc("Invalid month code syntax")] + UnknownMonthCode(MonthCode), + /// The specified month code does not exist in this calendar. + /// + /// # Examples + /// + /// ``` + /// use icu::calendar::Date; + /// use icu::calendar::DateError; /// use icu::calendar::cal::Hebrew; /// use icu::calendar::types::Month; + /// + /// let err = Date::try_new_from_codes(None, 5783, Month::new(13).code(), 1, Hebrew) + /// .expect_err("no month `M13` in Hebrew"); + /// assert_eq!(err, DateError::MonthNotInCalendar); + /// ``` + #[displaydoc("The specified month code does not exist in this calendar")] + MonthNotInCalendar, + /// The specified month code does not exist in this calendar. + /// + /// # Examples + /// + /// ``` /// use icu::calendar::Date; /// use icu::calendar::DateError; - /// use tinystr::tinystr; - /// - /// Date::try_new_from_codes( - /// None, - /// 5784, - /// Month::leap(5).code(), - /// 1, - /// Hebrew, - /// ) - /// .expect("5784 is a leap year"); + /// use icu::calendar::cal::Hebrew; + /// use icu::calendar::types::Month; /// - /// let err = Date::try_new_from_codes( - /// None, - /// 5785, - /// Month::leap(5).code(), - /// 1, - /// Hebrew, - /// ) - /// .expect_err("5785 is a common year"); + /// Date::try_new_from_codes(None, 5784, Month::leap(5).code(), 1, Hebrew) + /// .expect("5784 is a leap year"); /// - /// assert!(matches!(err, DateError::UnknownMonthCode(_))); + /// let err = Date::try_new_from_codes(None, 5785, Month::leap(5).code(), 1, Hebrew) + /// .expect_err("5785 is a common year"); + /// assert_eq!(err, DateError::MonthNotInYear); /// ``` - #[displaydoc("Unknown month code {0:?}")] - UnknownMonthCode(MonthCode), + #[displaydoc("The specified month code exists in calendar, but not for this year")] + MonthNotInYear, } impl core::error::Error for DateError {} @@ -107,11 +140,21 @@ mod unstable { /// /// assert!(matches!( /// err, - /// DateFromFieldsError::Range(RangeError { field: "month", .. }) - /// )); + /// DateFromFieldsError::Range { field: "month", .. }) + /// ); /// ``` - #[displaydoc("{0}")] - Range(RangeError), + #[displaydoc("The {field} = {value} argument is out of range {min}..={max}")] + #[non_exhaustive] + Range { + /// The field that is out of range, such as "year" + field: &'static str, + /// The actual value + value: i32, + /// The minimum value (inclusive). This might not be tight. + min: i32, + /// The maximum value (inclusive). This might not be tight. + max: i32, + }, /// The era code is invalid for the calendar. #[displaydoc("Unknown era or invalid syntax")] UnknownEra, @@ -217,7 +260,6 @@ mod unstable { /// use icu::calendar::error::DateFromFieldsError; /// use icu::calendar::types::DateFields; /// use icu::calendar::Date; - /// use tinystr::tinystr; /// /// let mut fields = DateFields::default(); /// fields.extended_year = Some(5783); @@ -246,7 +288,6 @@ mod unstable { /// use icu::calendar::error::DateFromFieldsError; /// use icu::calendar::types::DateFields; /// use icu::calendar::Date; - /// use tinystr::tinystr; /// /// let mut fields = DateFields::default(); /// @@ -287,7 +328,18 @@ mod unstable { impl From for DateFromFieldsError { #[inline] fn from(value: RangeError) -> Self { - DateFromFieldsError::Range(value) + let RangeError { + field, + value, + min, + max, + } = value; + DateFromFieldsError::Range { + field, + value, + min, + max, + } } } } @@ -333,6 +385,16 @@ pub(crate) enum MonthCodeError { NotInYear, } +impl From for DateError { + #[inline] + fn from(value: MonthCodeError) -> Self { + match value { + MonthCodeError::NotInCalendar => DateError::MonthNotInCalendar, + MonthCodeError::NotInYear => DateError::MonthNotInYear, + } + } +} + impl From for DateFromFieldsError { #[inline] fn from(value: MonthCodeError) -> Self { diff --git a/ffi/capi/bindings/c/CalendarError.d.h b/ffi/capi/bindings/c/CalendarError.d.h index 0247f0e5e17..41eab9af5f7 100644 --- a/ffi/capi/bindings/c/CalendarError.d.h +++ b/ffi/capi/bindings/c/CalendarError.d.h @@ -16,6 +16,8 @@ typedef enum CalendarError { CalendarError_OutOfRange = 1, CalendarError_UnknownEra = 2, CalendarError_UnknownMonthCode = 3, + CalendarError_MonthCodeNotInCalendar = 4, + CalendarError_MonthCodeNotInYear = 5, } CalendarError; typedef struct CalendarError_option {union { CalendarError ok; }; bool is_ok; } CalendarError_option; diff --git a/ffi/capi/bindings/cpp/icu4x/CalendarError.d.hpp b/ffi/capi/bindings/cpp/icu4x/CalendarError.d.hpp index 6c79b8e09cf..9f7ede6969d 100644 --- a/ffi/capi/bindings/cpp/icu4x/CalendarError.d.hpp +++ b/ffi/capi/bindings/cpp/icu4x/CalendarError.d.hpp @@ -19,6 +19,8 @@ namespace capi { CalendarError_OutOfRange = 1, CalendarError_UnknownEra = 2, CalendarError_UnknownMonthCode = 3, + CalendarError_MonthCodeNotInCalendar = 4, + CalendarError_MonthCodeNotInYear = 5, }; typedef struct CalendarError_option {union { CalendarError ok; }; bool is_ok; } CalendarError_option; @@ -36,6 +38,8 @@ class CalendarError { OutOfRange = 1, UnknownEra = 2, UnknownMonthCode = 3, + MonthCodeNotInCalendar = 4, + MonthCodeNotInYear = 5, }; CalendarError(): value(Value::Unknown) {} diff --git a/ffi/capi/bindings/cpp/icu4x/CalendarError.hpp b/ffi/capi/bindings/cpp/icu4x/CalendarError.hpp index e99e0671fda..ef303cec70c 100644 --- a/ffi/capi/bindings/cpp/icu4x/CalendarError.hpp +++ b/ffi/capi/bindings/cpp/icu4x/CalendarError.hpp @@ -30,6 +30,8 @@ inline icu4x::CalendarError icu4x::CalendarError::FromFFI(icu4x::capi::CalendarE case icu4x::capi::CalendarError_OutOfRange: case icu4x::capi::CalendarError_UnknownEra: case icu4x::capi::CalendarError_UnknownMonthCode: + case icu4x::capi::CalendarError_MonthCodeNotInCalendar: + case icu4x::capi::CalendarError_MonthCodeNotInYear: return static_cast(c_enum); default: std::abort(); diff --git a/ffi/capi/bindings/dart/CalendarError.g.dart b/ffi/capi/bindings/dart/CalendarError.g.dart index 8c9f031e104..02ff60b32cf 100644 --- a/ffi/capi/bindings/dart/CalendarError.g.dart +++ b/ffi/capi/bindings/dart/CalendarError.g.dart @@ -16,7 +16,13 @@ enum CalendarError { unknownEra, // ignore: public_member_api_docs - unknownMonthCode; + unknownMonthCode, + + // ignore: public_member_api_docs + monthCodeNotInCalendar, + + // ignore: public_member_api_docs + monthCodeNotInYear; } diff --git a/ffi/capi/bindings/js/CalendarError.d.ts b/ffi/capi/bindings/js/CalendarError.d.ts index 5eda5bb1ca1..7e88c57ed59 100644 --- a/ffi/capi/bindings/js/CalendarError.d.ts +++ b/ffi/capi/bindings/js/CalendarError.d.ts @@ -20,6 +20,8 @@ export class CalendarError { static OutOfRange : CalendarError; static UnknownEra : CalendarError; static UnknownMonthCode : CalendarError; + static MonthCodeNotInCalendar : CalendarError; + static MonthCodeNotInYear : CalendarError; constructor(value: CalendarError | string ); diff --git a/ffi/capi/bindings/js/CalendarError.mjs b/ffi/capi/bindings/js/CalendarError.mjs index 7cd3d526fdc..cdbcac12906 100644 --- a/ffi/capi/bindings/js/CalendarError.mjs +++ b/ffi/capi/bindings/js/CalendarError.mjs @@ -14,7 +14,9 @@ export class CalendarError { ["Unknown", 0], ["OutOfRange", 1], ["UnknownEra", 2], - ["UnknownMonthCode", 3] + ["UnknownMonthCode", 3], + ["MonthCodeNotInCalendar", 4], + ["MonthCodeNotInYear", 5] ]); static getAllEntries() { @@ -64,12 +66,16 @@ export class CalendarError { new CalendarError(diplomatRuntime.internalConstructor, diplomatRuntime.internalConstructor, 1), new CalendarError(diplomatRuntime.internalConstructor, diplomatRuntime.internalConstructor, 2), new CalendarError(diplomatRuntime.internalConstructor, diplomatRuntime.internalConstructor, 3), + new CalendarError(diplomatRuntime.internalConstructor, diplomatRuntime.internalConstructor, 4), + new CalendarError(diplomatRuntime.internalConstructor, diplomatRuntime.internalConstructor, 5), ]; static Unknown = CalendarError.#objectValues[0]; static OutOfRange = CalendarError.#objectValues[1]; static UnknownEra = CalendarError.#objectValues[2]; static UnknownMonthCode = CalendarError.#objectValues[3]; + static MonthCodeNotInCalendar = CalendarError.#objectValues[4]; + static MonthCodeNotInYear = CalendarError.#objectValues[5]; constructor(value) { diff --git a/ffi/capi/src/errors.rs b/ffi/capi/src/errors.rs index ce94f14d500..c39963daccb 100644 --- a/ffi/capi/src/errors.rs +++ b/ffi/capi/src/errors.rs @@ -69,6 +69,8 @@ pub mod ffi { OutOfRange = 0x01, UnknownEra = 0x02, UnknownMonthCode = 0x03, + MonthCodeNotInCalendar = 0x04, + MonthCodeNotInYear = 0x05, } #[derive(Debug, PartialEq, Eq)] @@ -202,6 +204,8 @@ impl From for CalendarError { icu_calendar::DateError::Range { .. } => Self::OutOfRange, icu_calendar::DateError::UnknownEra => Self::UnknownEra, icu_calendar::DateError::UnknownMonthCode(..) => Self::UnknownMonthCode, + icu_calendar::DateError::MonthNotInCalendar => Self::MonthCodeNotInCalendar, + icu_calendar::DateError::MonthNotInYear => Self::MonthCodeNotInYear, _ => Self::Unknown, } } @@ -212,7 +216,7 @@ impl From for CalendarError { impl From for CalendarDateFromFieldsError { fn from(e: icu_calendar::error::DateFromFieldsError) -> Self { match e { - icu_calendar::error::DateFromFieldsError::Range(_) => Self::OutOfRange, + icu_calendar::error::DateFromFieldsError::Range { .. } => Self::OutOfRange, icu_calendar::error::DateFromFieldsError::UnknownEra => Self::UnknownEra, icu_calendar::error::DateFromFieldsError::MonthCodeInvalidSyntax => { Self::MonthCodeInvalidSyntax