icu_calendar/error.rs
1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5//! Error types for functions in the calendar crate
6
7use crate::{options::Overflow, types::MonthCode};
8use displaydoc::Display;
9
10#[derive(Debug, Copy, Clone, PartialEq, Display)]
11/// Error type for date creation via [`Date::try_new_from_codes`].
12///
13/// [`Date::try_new_from_codes`]: crate::Date::try_new_from_codes
14#[non_exhaustive]
15pub enum DateError {
16 /// A field is out of range for its domain.
17 #[displaydoc("The {field} = {value} argument is out of range {min}..={max}")]
18 Range {
19 /// The field that is out of range, such as "year"
20 field: &'static str,
21 /// The actual value
22 value: i32,
23 /// The minimum value (inclusive). This might not be tight.
24 min: i32,
25 /// The maximum value (inclusive). This might not be tight.
26 max: i32,
27 },
28 /// The era code is invalid for the calendar.
29 #[displaydoc("Unknown era")]
30 UnknownEra,
31 /// The month code is invalid for the calendar or year.
32 ///
33 /// # Examples
34 ///
35 /// ```
36 /// use icu::calendar::cal::Hebrew;
37 /// use icu::calendar::types::MonthCode;
38 /// use icu::calendar::Date;
39 /// use icu::calendar::DateError;
40 /// use tinystr::tinystr;
41 ///
42 /// Date::try_new_from_codes(
43 /// None,
44 /// 5784,
45 /// MonthCode::new_leap(5).unwrap(),
46 /// 1,
47 /// Hebrew,
48 /// )
49 /// .expect("5784 is a leap year");
50 ///
51 /// let err = Date::try_new_from_codes(
52 /// None,
53 /// 5785,
54 /// MonthCode::new_leap(5).unwrap(),
55 /// 1,
56 /// Hebrew,
57 /// )
58 /// .expect_err("5785 is a common year");
59 ///
60 /// assert!(matches!(err, DateError::UnknownMonthCode(_)));
61 /// ```
62 #[displaydoc("Unknown month code {0:?}")]
63 UnknownMonthCode(MonthCode),
64}
65
66impl core::error::Error for DateError {}
67#[cfg(feature = "unstable")]
68pub use unstable::DateFromFieldsError;
69#[cfg(not(feature = "unstable"))]
70pub(crate) use unstable::DateFromFieldsError;
71
72mod unstable {
73 pub use super::*;
74 /// Error type for date creation via [`Date::try_from_fields`].
75 ///
76 /// [`Date::try_from_fields`]: crate::Date::try_from_fields
77 ///
78 /// <div class="stab unstable">
79 /// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
80 /// including in SemVer minor releases. Do not use this type unless you are prepared for things to occasionally break.
81 ///
82 /// Graduation tracking issue: [issue #7161](https://github.com/unicode-org/icu4x/issues/7161).
83 /// </div>
84 ///
85 /// ✨ *Enabled with the `unstable` Cargo feature.*
86 #[derive(Debug, Copy, Clone, PartialEq, Display)]
87 #[non_exhaustive]
88 pub enum DateFromFieldsError {
89 /// A field is out of range for its domain.
90 ///
91 /// # Examples
92 ///
93 /// ```
94 /// use icu::calendar::error::DateFromFieldsError;
95 /// use icu::calendar::error::RangeError;
96 /// use icu::calendar::types::DateFields;
97 /// use icu::calendar::Date;
98 /// use icu::calendar::Iso;
99 ///
100 /// let mut fields = DateFields::default();
101 /// fields.extended_year = Some(2000);
102 /// fields.ordinal_month = Some(13);
103 /// fields.day = Some(1);
104 ///
105 /// let err = Date::try_from_fields(fields, Default::default(), Iso)
106 /// .expect_err("no month 13 in the ISO calendar");
107 ///
108 /// assert!(matches!(
109 /// err,
110 /// DateFromFieldsError::Range(RangeError { field: "month", .. })
111 /// ));
112 /// ```
113 #[displaydoc("{0}")]
114 Range(RangeError),
115 /// The era code is invalid for the calendar.
116 #[displaydoc("Unknown era or invalid syntax")]
117 UnknownEra,
118 /// The month code syntax is invalid.
119 ///
120 /// # Examples
121 ///
122 /// ```
123 /// use icu::calendar::error::DateFromFieldsError;
124 /// use icu::calendar::types::DateFields;
125 /// use icu::calendar::Date;
126 /// use icu::calendar::Iso;
127 ///
128 /// let mut fields = DateFields::default();
129 /// fields.extended_year = Some(2000);
130 /// fields.month_code = Some(b"????");
131 /// fields.day = Some(1);
132 ///
133 /// let err = Date::try_from_fields(fields, Default::default(), Iso)
134 /// .expect_err("month code is invalid");
135 ///
136 /// assert_eq!(err, DateFromFieldsError::MonthCodeInvalidSyntax);
137 /// ```
138 #[displaydoc("Invalid month code syntax")]
139 MonthCodeInvalidSyntax,
140 /// The specified month code does not exist in this calendar.
141 ///
142 /// # Examples
143 ///
144 /// ```
145 /// use icu::calendar::cal::Hebrew;
146 /// use icu::calendar::error::DateFromFieldsError;
147 /// use icu::calendar::types::DateFields;
148 /// use icu::calendar::Date;
149 ///
150 /// let mut fields = DateFields::default();
151 /// fields.extended_year = Some(5783);
152 /// fields.month_code = Some(b"M13");
153 /// fields.day = Some(1);
154 ///
155 /// let err = Date::try_from_fields(fields, Default::default(), Hebrew)
156 /// .expect_err("no month M13 in Hebrew");
157 /// assert_eq!(err, DateFromFieldsError::MonthCodeNotInCalendar);
158 /// ```
159 #[displaydoc("The specified month code does not exist in this calendar")]
160 MonthCodeNotInCalendar,
161 /// The specified month code exists in this calendar, but not in the specified year.
162 ///
163 /// # Examples
164 ///
165 /// ```
166 /// use icu::calendar::cal::Hebrew;
167 /// use icu::calendar::error::DateFromFieldsError;
168 /// use icu::calendar::types::DateFields;
169 /// use icu::calendar::Date;
170 ///
171 /// let mut fields = DateFields::default();
172 /// fields.extended_year = Some(5783);
173 /// fields.month_code = Some(b"M05L");
174 /// fields.day = Some(1);
175 ///
176 /// let err = Date::try_from_fields(fields, Default::default(), Hebrew)
177 /// .expect_err("no month M05L in Hebrew year 5783");
178 /// assert_eq!(err, DateFromFieldsError::MonthCodeNotInYear);
179 /// ```
180 #[displaydoc("The specified month code exists in calendar, but not for this year")]
181 MonthCodeNotInYear,
182 /// The year was specified in multiple inconsistent ways.
183 ///
184 /// # Examples
185 ///
186 /// ```
187 /// use icu::calendar::cal::Japanese;
188 /// use icu::calendar::error::DateFromFieldsError;
189 /// use icu::calendar::types::DateFields;
190 /// use icu::calendar::Date;
191 ///
192 /// let mut fields = DateFields::default();
193 /// fields.era = Some(b"reiwa");
194 /// fields.era_year = Some(6);
195 /// fields.ordinal_month = Some(1);
196 /// fields.day = Some(1);
197 ///
198 /// Date::try_from_fields(fields, Default::default(), Japanese::new())
199 /// .expect("a well-defined Japanese date");
200 ///
201 /// fields.extended_year = Some(1900);
202 ///
203 /// let err =
204 /// Date::try_from_fields(fields, Default::default(), Japanese::new())
205 /// .expect_err("year 1900 is not the same as 6 Reiwa");
206 ///
207 /// assert_eq!(err, DateFromFieldsError::InconsistentYear);
208 /// ```
209 #[displaydoc("Inconsistent year")]
210 InconsistentYear,
211 /// The month was specified in multiple inconsistent ways.
212 ///
213 /// # Examples
214 ///
215 /// ```
216 /// use icu::calendar::cal::Hebrew;
217 /// use icu::calendar::error::DateFromFieldsError;
218 /// use icu::calendar::types::DateFields;
219 /// use icu::calendar::Date;
220 /// use tinystr::tinystr;
221 ///
222 /// let mut fields = DateFields::default();
223 /// fields.extended_year = Some(5783);
224 /// fields.month_code = Some(b"M06");
225 /// fields.ordinal_month = Some(6);
226 /// fields.day = Some(1);
227 ///
228 /// Date::try_from_fields(fields, Default::default(), Hebrew)
229 /// .expect("a well-defined Hebrew date in a common year");
230 ///
231 /// fields.extended_year = Some(5784);
232 ///
233 /// let err = Date::try_from_fields(fields, Default::default(), Hebrew)
234 /// .expect_err("month M06 is not the 6th month in leap year 5784");
235 ///
236 /// assert_eq!(err, DateFromFieldsError::InconsistentMonth);
237 /// ```
238 #[displaydoc("Inconsistent month")]
239 InconsistentMonth,
240 /// Not enough fields were specified to form a well-defined date.
241 ///
242 /// # Examples
243 ///
244 /// ```
245 /// use icu::calendar::cal::Hebrew;
246 /// use icu::calendar::error::DateFromFieldsError;
247 /// use icu::calendar::types::DateFields;
248 /// use icu::calendar::Date;
249 /// use tinystr::tinystr;
250 ///
251 /// let mut fields = DateFields::default();
252 ///
253 /// fields.ordinal_month = Some(3);
254 ///
255 /// let err = Date::try_from_fields(fields, Default::default(), Hebrew)
256 /// .expect_err("need more than just an ordinal month");
257 /// assert_eq!(err, DateFromFieldsError::NotEnoughFields);
258 ///
259 /// fields.era_year = Some(5783);
260 ///
261 /// let err = Date::try_from_fields(fields, Default::default(), Hebrew)
262 /// .expect_err("need more than an ordinal month and an era year");
263 /// assert_eq!(err, DateFromFieldsError::NotEnoughFields);
264 ///
265 /// fields.extended_year = Some(5783);
266 ///
267 /// let err = Date::try_from_fields(fields, Default::default(), Hebrew)
268 /// .expect_err("era year still needs an era");
269 /// assert_eq!(err, DateFromFieldsError::NotEnoughFields);
270 ///
271 /// fields.era = Some(b"am");
272 ///
273 /// let date = Date::try_from_fields(fields, Default::default(), Hebrew)
274 /// .expect_err("still missing the day");
275 /// assert_eq!(err, DateFromFieldsError::NotEnoughFields);
276 ///
277 /// fields.day = Some(1);
278 /// let date = Date::try_from_fields(fields, Default::default(), Hebrew)
279 /// .expect("we have enough fields!");
280 /// ```
281 #[displaydoc("Not enough fields")]
282 NotEnoughFields,
283 }
284
285 impl core::error::Error for DateFromFieldsError {}
286
287 impl From<RangeError> for DateFromFieldsError {
288 #[inline]
289 fn from(value: RangeError) -> Self {
290 DateFromFieldsError::Range(value)
291 }
292 }
293}
294
295/// Internal narrow error type for functions that only fail on unknown eras
296pub(crate) struct UnknownEraError;
297
298impl From<UnknownEraError> for DateError {
299 #[inline]
300 fn from(_value: UnknownEraError) -> Self {
301 DateError::UnknownEra
302 }
303}
304
305impl From<UnknownEraError> for DateFromFieldsError {
306 #[inline]
307 fn from(_value: UnknownEraError) -> Self {
308 DateFromFieldsError::UnknownEra
309 }
310}
311
312/// Internal narrow error type for functions that only fail on parsing month codes
313#[derive(Debug)]
314pub(crate) enum MonthCodeParseError {
315 InvalidSyntax,
316}
317
318impl From<MonthCodeParseError> for DateFromFieldsError {
319 #[inline]
320 fn from(value: MonthCodeParseError) -> Self {
321 match value {
322 MonthCodeParseError::InvalidSyntax => DateFromFieldsError::MonthCodeInvalidSyntax,
323 }
324 }
325}
326
327/// Internal narrow error type for functions that only fail on month code operations
328#[derive(Debug, PartialEq)]
329pub(crate) enum MonthCodeError {
330 NotInCalendar,
331 NotInYear,
332}
333
334impl From<MonthCodeError> for DateFromFieldsError {
335 #[inline]
336 fn from(value: MonthCodeError) -> Self {
337 match value {
338 MonthCodeError::NotInCalendar => DateFromFieldsError::MonthCodeNotInCalendar,
339 MonthCodeError::NotInYear => DateFromFieldsError::MonthCodeNotInYear,
340 }
341 }
342}
343
344mod inner {
345 /// Internal narrow error type for calculating the ECMA reference year
346 ///
347 /// Public but unstable because it is used on hijri::Rules
348 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
349 #[allow(missing_docs)] // TODO: fix when graduating
350 #[non_exhaustive]
351 pub enum EcmaReferenceYearError {
352 Unimplemented,
353 MonthCodeNotInCalendar,
354 }
355}
356
357#[cfg(feature = "unstable")]
358pub use inner::EcmaReferenceYearError;
359#[cfg(not(feature = "unstable"))]
360pub(crate) use inner::EcmaReferenceYearError;
361
362impl From<EcmaReferenceYearError> for DateFromFieldsError {
363 #[inline]
364 fn from(value: EcmaReferenceYearError) -> Self {
365 match value {
366 EcmaReferenceYearError::Unimplemented => DateFromFieldsError::NotEnoughFields,
367 EcmaReferenceYearError::MonthCodeNotInCalendar => {
368 DateFromFieldsError::MonthCodeNotInCalendar
369 }
370 }
371 }
372}
373
374#[derive(Debug, Copy, Clone, PartialEq, Display)]
375/// An argument is out of range for its domain.
376#[displaydoc("The {field} = {value} argument is out of range {min}..={max}")]
377#[allow(clippy::exhaustive_structs)]
378pub struct RangeError {
379 /// The argument that is out of range, such as "year"
380 pub field: &'static str,
381 /// The actual value
382 pub value: i32,
383 /// The minimum value (inclusive). This might not be tight.
384 pub min: i32,
385 /// The maximum value (inclusive). This might not be tight.
386 pub max: i32,
387}
388
389impl core::error::Error for RangeError {}
390
391impl From<RangeError> for DateError {
392 #[inline]
393 fn from(value: RangeError) -> Self {
394 let RangeError {
395 field,
396 value,
397 min,
398 max,
399 } = value;
400 DateError::Range {
401 field,
402 value,
403 min,
404 max,
405 }
406 }
407}
408
409pub(crate) fn range_check_with_overflow<T: Ord + Into<i32> + Copy>(
410 value: T,
411 field: &'static str,
412 bounds: core::ops::RangeInclusive<T>,
413 overflow: Overflow,
414) -> Result<T, RangeError> {
415 if matches!(overflow, Overflow::Constrain) {
416 Ok(value.clamp(*bounds.start(), *bounds.end()))
417 } else {
418 range_check(value, field, bounds)
419 }
420}
421
422pub(crate) fn range_check<T: Ord + Into<i32> + Copy>(
423 value: T,
424 field: &'static str,
425 bounds: core::ops::RangeInclusive<T>,
426) -> Result<T, RangeError> {
427 if !bounds.contains(&value) {
428 return Err(RangeError {
429 field,
430 value: value.into(),
431 min: (*bounds.start()).into(),
432 max: (*bounds.end()).into(),
433 });
434 }
435 Ok(value)
436}