use core::fmt;
use ixdtf::ParseError;
use timezone_provider::TimeZoneProviderError;
use icu_calendar::error::{DateAddError, DateFromFieldsError};
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub enum ErrorKind {
#[default]
Generic,
Type,
Range,
Syntax,
Assert,
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Generic => "Error",
Self::Type => "TypeError",
Self::Range => "RangeError",
Self::Syntax => "SyntaxError",
Self::Assert => "ImplementationError",
}
.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TemporalError {
kind: ErrorKind,
msg: ErrorMessage,
}
impl TemporalError {
#[inline]
#[must_use]
const fn new(kind: ErrorKind) -> Self {
Self {
kind,
msg: ErrorMessage::None,
}
}
#[inline]
#[must_use]
pub fn general(msg: &'static str) -> Self {
Self::new(ErrorKind::Generic).with_message(msg)
}
#[inline]
#[must_use]
pub const fn range() -> Self {
Self::new(ErrorKind::Range)
}
#[inline]
#[must_use]
pub const fn r#type() -> Self {
Self::new(ErrorKind::Type)
}
#[inline]
#[must_use]
pub const fn syntax() -> Self {
Self::new(ErrorKind::Syntax)
}
#[inline]
#[must_use]
#[cfg_attr(debug_assertions, track_caller)]
pub(crate) const fn assert() -> Self {
#[cfg(not(debug_assertions))]
{
Self::new(ErrorKind::Assert)
}
#[cfg(debug_assertions)]
Self {
kind: ErrorKind::Assert,
msg: ErrorMessage::String(core::panic::Location::caller().file()),
}
}
#[inline]
#[must_use]
pub fn abrupt_end() -> Self {
Self::syntax().with_message("Abrupt end to parsing target.")
}
#[inline]
#[must_use]
pub fn with_message(mut self, msg: &'static str) -> Self {
self.msg = ErrorMessage::String(msg);
self
}
#[inline]
#[must_use]
pub(crate) fn with_enum(mut self, msg: ErrorMessage) -> Self {
self.msg = msg;
self
}
#[inline]
#[must_use]
pub const fn kind(&self) -> ErrorKind {
self.kind
}
#[inline]
#[must_use]
pub fn into_message(self) -> &'static str {
self.msg.to_string()
}
}
impl core::error::Error for TemporalError {}
impl fmt::Display for TemporalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)?;
let msg = self.msg.to_string();
if !msg.is_empty() {
write!(f, ": {msg}")?;
}
Ok(())
}
}
impl From<DateFromFieldsError> for TemporalError {
fn from(error: DateFromFieldsError) -> Self {
let kind = if error == DateFromFieldsError::NotEnoughFields {
ErrorKind::Type
} else {
ErrorKind::Range
};
TemporalError::new(kind).with_enum(ErrorMessage::Icu4xDateFromFields(error))
}
}
impl From<DateAddError> for TemporalError {
fn from(error: DateAddError) -> Self {
TemporalError::range().with_enum(ErrorMessage::Icu4xDateAdd(error))
}
}
impl From<ParseError> for TemporalError {
fn from(error: ParseError) -> Self {
TemporalError::range().with_enum(ErrorMessage::Ixdtf(error))
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub(crate) enum ErrorMessage {
InstantOutOfRange,
IntermediateDateTimeOutOfRange,
ZDTOutOfDayBounds,
LargestUnitCannotBeDateUnit,
DateOutOfRange,
DurationNotValid,
NumberNotFinite,
NumberNotIntegral,
NumberNotPositive,
NumberOutOfRange,
FractionalDigitsPrecisionInvalid,
SmallestUnitIsRequired,
SmallestUnitNotTimeUnit,
SmallestUnitLargerThanLargestUnit,
UnitNotDate,
UnitNotTime,
UnitRequired,
UnitNoAutoDuringComparison,
RoundToUnitInvalid,
RoundingModeInvalid,
CalendarNameInvalid,
OffsetOptionInvalid,
TimeZoneNameInvalid,
CalendarMismatch,
TzMismatch,
EmptyFieldsIsInvalid,
ParserNeedsDate,
FractionalTimeMoreThanNineDigits,
OffsetNeedsDisambiguation,
None,
String(&'static str),
Ixdtf(ParseError),
Icu4xDateFromFields(DateFromFieldsError),
Icu4xDateAdd(DateAddError),
}
impl ErrorMessage {
pub fn to_string(self) -> &'static str {
match self {
Self::InstantOutOfRange => "Instant nanoseconds are not within a valid epoch range.",
Self::IntermediateDateTimeOutOfRange => {
"Intermediate ISO datetime was not within a valid range."
}
Self::ZDTOutOfDayBounds => "ZonedDateTime is outside the expected day bounds",
Self::LargestUnitCannotBeDateUnit => "Largest unit cannot be a date unit",
Self::DateOutOfRange => "Date is not within ISO date time limits.",
Self::DurationNotValid => "Duration was not valid.",
Self::NumberNotFinite => "number value is not a finite value.",
Self::NumberNotIntegral => "value must be integral.",
Self::NumberNotPositive => "integer must be positive.",
Self::NumberOutOfRange => "number exceeded a valid range.",
Self::FractionalDigitsPrecisionInvalid => "Invalid fractionalDigits precision value",
Self::SmallestUnitIsRequired => "smallestUnit is required",
Self::SmallestUnitNotTimeUnit => "smallestUnit must be a valid time unit.",
Self::SmallestUnitLargerThanLargestUnit => {
"smallestUnit was larger than largestunit in DifferenceeSettings"
}
Self::UnitNotDate => "Unit was not part of the date unit group.",
Self::UnitNotTime => "Unit was not part of the time unit group.",
Self::UnitRequired => "Unit is required",
Self::UnitNoAutoDuringComparison => "'auto' units are not allowed during comparison",
Self::RoundToUnitInvalid => "Invalid roundTo unit provided.",
Self::RoundingModeInvalid => "Invalid roundingMode option provided",
Self::CalendarNameInvalid => "Invalid calendarName option provided",
Self::OffsetOptionInvalid => "Invalid offsetOption option provided",
Self::TimeZoneNameInvalid => "Invalid timeZoneName option provided",
Self::CalendarMismatch => {
"Calendar must be the same for operations involving two calendared types."
}
Self::TzMismatch => "Timezones must be the same if unit is a day unit.",
Self::EmptyFieldsIsInvalid => "fields cannot be empty",
Self::ParserNeedsDate => "Could not find a valid DateRecord node during parsing.",
Self::FractionalTimeMoreThanNineDigits => "Fractional time exceeds nine digits.",
Self::OffsetNeedsDisambiguation => {
"Offsets could not be determined without disambiguation"
}
Self::None => "",
Self::String(s) => s,
Self::Ixdtf(s) => ixdtf_error_to_static_string(s),
Self::Icu4xDateFromFields(DateFromFieldsError::InvalidEra) => "Unknown era.",
Self::Icu4xDateFromFields(DateFromFieldsError::InvalidDay { .. })
| Self::Icu4xDateAdd(DateAddError::InvalidDay { .. }) => "Day out of range",
Self::Icu4xDateFromFields(DateFromFieldsError::InvalidOrdinalMonth { .. }) => {
"Month out of range"
}
Self::Icu4xDateFromFields(DateFromFieldsError::MonthCodeInvalidSyntax) => {
"Invalid month code."
}
Self::Icu4xDateFromFields(DateFromFieldsError::MonthNotInCalendar) => {
"Month code not in calendar."
}
Self::Icu4xDateFromFields(DateFromFieldsError::MonthNotInYear)
| Self::Icu4xDateAdd(DateAddError::MonthNotInYear) => "Month code not in year.",
Self::Icu4xDateFromFields(DateFromFieldsError::InconsistentYear) => {
"Inconsistent year."
}
Self::Icu4xDateFromFields(DateFromFieldsError::InconsistentMonth) => {
"Inconsistent month/monthCode."
}
Self::Icu4xDateFromFields(DateFromFieldsError::NotEnoughFields) => {
"Insufficient fields."
}
Self::Icu4xDateAdd(DateAddError::Overflow) => "Overflow during addition.",
Self::Icu4xDateFromFields(_) | Self::Icu4xDateAdd(_) => "Date error.",
}
}
}
impl From<TimeZoneProviderError> for TemporalError {
fn from(other: TimeZoneProviderError) -> Self {
match other {
TimeZoneProviderError::InstantOutOfRange => {
Self::range().with_enum(ErrorMessage::InstantOutOfRange)
}
TimeZoneProviderError::Assert(s) => Self::assert().with_message(s),
TimeZoneProviderError::Range(s) => Self::range().with_message(s),
_ => Self::assert().with_message("Unknown TimeZoneProviderError"),
}
}
}
pub fn ixdtf_error_to_static_string(error: ParseError) -> &'static str {
match error {
ParseError::ImplAssert => "Implementation error: this error must not throw.",
ParseError::NonAsciiCodePoint => "Code point was not ASCII",
ParseError::ParseFloat => "Invalid float while parsing fraction part.",
ParseError::AbruptEnd { .. } => "Parsing ended abruptly.",
ParseError::InvalidEnd => "Unexpected character found after parsing was completed.",
ParseError::InvalidMonthRange => "Parsed month value not in a valid range.",
ParseError::InvalidDayRange => "Parsed day value not in a valid range.",
ParseError::DateYear => "Invalid character while parsing year value.",
ParseError::DateExtendedYear => "Invalid character while parsing extended year value.",
ParseError::DateMonth => "Invalid character while parsing month value.",
ParseError::DateDay => "Invalid character while parsing day value.",
ParseError::DateUnexpectedEnd => "Unexpected end while parsing a date value.",
ParseError::TimeRequired => "Time is required.",
ParseError::TimeHour => "Invalid character while parsing hour value.",
ParseError::TimeMinuteSecond => {
"Invalid character while parsing minute/second value in (0, 59] range."
}
ParseError::TimeSecond => "Invalid character while parsing second value in (0, 60] range.",
ParseError::FractionPart => "Invalid character while parsing fraction part value.",
ParseError::DateSeparator => "Invalid character while parsing date separator.",
ParseError::TimeSeparator => "Invalid character while parsing time separator.",
ParseError::DecimalSeparator => "Invalid character while parsing decimal separator.",
ParseError::InvalidAnnotation => "Invalid annotation.",
ParseError::AnnotationOpen => "Invalid annotation open character.",
ParseError::AnnotationClose => "Invalid annotation close character.",
ParseError::AnnotationChar => "Invalid annotation character.",
ParseError::AnnotationKeyValueSeparator => {
"Invalid annotation key-value separator character."
}
ParseError::AnnotationKeyLeadingChar => "Invalid annotation key leading character.",
ParseError::AnnotationKeyChar => "Invalid annotation key character.",
ParseError::AnnotationValueCharPostHyphen => {
"Expected annotation value character must exist after hyphen."
}
ParseError::AnnotationValueChar => "Invalid annotation value character.",
ParseError::InvalidMinutePrecisionOffset => "Offset must be minute precision",
ParseError::CriticalDuplicateCalendar => {
"Duplicate calendars cannot be provided when one is critical."
}
ParseError::UnrecognizedCritical => "Unrecognized annoation is marked as critical.",
ParseError::TzLeadingChar => "Invalid time zone leading character.",
ParseError::IanaCharPostSeparator => "Expected time zone character after '/'.",
ParseError::IanaChar => "Invalid IANA time zone character after '/'.",
ParseError::UtcTimeSeparator => "Invalid time zone character after '/'.",
ParseError::OffsetNeedsSign => "UTC offset needs a sign",
ParseError::MonthDayHyphen => "MonthDay must begin with a month or '--'",
ParseError::DurationDisgnator => "Invalid duration designator.",
ParseError::DurationValueExceededRange => {
"Provided Duration field value exceeds supported range."
}
ParseError::DateDurationPartOrder => "Invalid date duration part order.",
ParseError::TimeDurationPartOrder => "Invalid time duration part order.",
ParseError::TimeDurationDesignator => "Invalid time duration designator.",
ParseError::AmbiguousTimeMonthDay => "Time is ambiguous with MonthDay",
ParseError::AmbiguousTimeYearMonth => "Time is ambiguous with YearMonth",
ParseError::InvalidMonthDay => "MonthDay was not valid.",
_ => "General IXDTF parsing error",
}
}