1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
#![allow(clippy::module_name_repetitions)]

use core::fmt;
use core::num::TryFromIntError;
use core::time::TryFromFloatSecsError;
use std::error;
use std::str::Utf8Error;

use tz::error::{DateTimeError, ProjectDateTimeError, TzError};

/// A wrapper around some of the errors provided by `tz-rs`.
#[derive(Debug)]
pub enum TimeError {
    /// Created when trying to create a `DateTime`, however the projection to a
    /// UNIX timestamp wasn't achievable. Generally thrown when exceeding the
    /// range of integers (e.g. `> i64::Max`).
    ///
    /// Note: This is just a wrapper over [`tz::error::ProjectDateTimeError`].
    ProjectionError(ProjectDateTimeError),

    /// Created when one of the parameters of a `DateTime` falls outside the
    /// allowed ranges (e.g. 13th month, 32 day, 24th hour, etc).
    ///
    /// Note: [`tz::error::DateTimeError`] is only thrown from `tz-rs` when a
    /// provided component value is out of range.
    ///
    /// Note: This is different from how MRI ruby is implemented. e.g. Second
    /// 60 is valid in MRI, and will just add an additional second instead of
    /// erroring.
    ComponentOutOfRangeError(DateTimeError),

    /// A rescuable error originally from the `tz-rs` library.
    UnknownTzError(TzError),

    /// Indicates that there was an issue when parsing a string for an offset.
    TzStringError(TzStringError),

    /// The provided tz offset seconds offset is outside of the allowed range.
    TzOutOfRangeError(TzOutOfRangeError),

    /// A rescuable Integer overflow error. Caused when trying to exceed the
    /// bounds of an int.
    IntOverflowError(IntOverflowError),

    /// An rescuable unknown error (instead of panicking).
    Unknown,
}

impl PartialEq for TimeError {
    fn eq(&self, other: &Self) -> bool {
        match self {
            Self::ProjectionError(_) => matches!(other, TimeError::ProjectionError(_)),
            Self::ComponentOutOfRangeError(_) => matches!(other, TimeError::ComponentOutOfRangeError(_)),
            Self::UnknownTzError(_) => matches!(other, TimeError::UnknownTzError(_)),
            Self::TzStringError(_) => matches!(other, TimeError::TzStringError(_)),
            Self::TzOutOfRangeError(_) => matches!(other, TimeError::TzOutOfRangeError(_)),
            Self::IntOverflowError(_) => matches!(other, TimeError::IntOverflowError(_)),
            Self::Unknown => matches!(other, TimeError::Unknown),
        }
    }
}

impl error::Error for TimeError {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match self {
            Self::ProjectionError(err) => Some(err),
            Self::ComponentOutOfRangeError(err) => Some(err),
            Self::UnknownTzError(err) => Some(err),
            Self::TzStringError(err) => Some(err),
            Self::TzOutOfRangeError(err) => Some(err),
            Self::IntOverflowError(err) => Some(err),
            Self::Unknown => None,
        }
    }
}

impl fmt::Display for TimeError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::ProjectionError(error) => error.fmt(f),
            Self::ComponentOutOfRangeError(error) => error.fmt(f),
            Self::UnknownTzError(error) => error.fmt(f),
            Self::TzStringError(error) => error.fmt(f),
            Self::TzOutOfRangeError(error) => error.fmt(f),
            Self::IntOverflowError(error) => error.fmt(f),
            Self::Unknown => write!(f, "An unknown error occurred"),
        }
    }
}

impl From<ProjectDateTimeError> for TimeError {
    fn from(err: ProjectDateTimeError) -> Self {
        Self::ProjectionError(err)
    }
}

impl From<DateTimeError> for TimeError {
    fn from(err: DateTimeError) -> Self {
        Self::ComponentOutOfRangeError(err)
    }
}

impl From<TzError> for TimeError {
    fn from(error: TzError) -> Self {
        // Allowing matching arms due to documentation
        #[allow(clippy::match_same_arms)]
        match error {
            // These two are generally recoverable within the usable of `spinoso_time`
            // TzError::DateTimeError(error) => Self::from(error),
            TzError::ProjectDateTimeError(error) => Self::from(error),

            // The rest will bleed through, but are included here for reference
            // Occurs when calling system clock
            TzError::SystemTimeError(_) => Self::UnknownTzError(error),
            // Occurs during parsing of TZif files
            TzError::TzFileError(_) => Self::UnknownTzError(error),
            // Occurs during parsing of TZif files (POSIX string parsing)
            TzError::TzStringError(_) => Self::UnknownTzError(error),
            // Occurs during int conversion (e.g. `i64` => `i32`)
            TzError::OutOfRangeError(_) => Self::UnknownTzError(error),
            // Occurs during creation of `TimeZoneRef`
            TzError::LocalTimeTypeError(_) => Self::UnknownTzError(error),
            // Occurs during creation of `TimeZoneRef`
            TzError::TransitionRuleError(_) => Self::UnknownTzError(error),
            // Occurs during creation of `TimeZoneRef`
            TzError::TimeZoneError(_) => Self::UnknownTzError(error),
            // Wrapped by `ProjectDateTimeError`
            TzError::FindLocalTimeTypeError(_) => Self::UnknownTzError(error),
            // Never explicitly returned
            TzError::IoError(_) => Self::UnknownTzError(error),
            // Never explicitly returned
            TzError::Utf8Error(_) => Self::UnknownTzError(error),
            // Never explicitly returned
            TzError::TryFromSliceError(_) => Self::UnknownTzError(error),
            // `TzError` is non-exhaustive, so the rest are caught as `Unknown`
            _ => Self::Unknown,
        }
    }
}

impl From<TzStringError> for TimeError {
    fn from(err: TzStringError) -> Self {
        Self::TzStringError(err)
    }
}

impl From<TzOutOfRangeError> for TimeError {
    fn from(err: TzOutOfRangeError) -> Self {
        Self::TzOutOfRangeError(err)
    }
}

impl From<IntOverflowError> for TimeError {
    fn from(err: IntOverflowError) -> Self {
        Self::IntOverflowError(err)
    }
}

impl From<TryFromFloatSecsError> for TimeError {
    fn from(err: TryFromFloatSecsError) -> Self {
        Self::TzOutOfRangeError(err.into())
    }
}

/// Error that indicates that the provided string cannot be used in the creation
/// of a timezone.
///
/// This error is returned by [`Offset::try_from`].
///
/// [`Offset::try_from`]: super::Offset::try_from
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct TzStringError {
    _private: (),
}

impl fmt::Display for TzStringError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        f.write_str(r#""+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset"#)
    }
}
impl error::Error for TzStringError {}

impl TzStringError {
    // This error is not to be constructed outside of this crate.
    pub(crate) const fn new() -> Self {
        Self { _private: () }
    }
}

impl From<Utf8Error> for TzStringError {
    fn from(_: Utf8Error) -> Self {
        TzStringError::new()
    }
}

/// Error that indicates that a provided value is outside the allowed range of
/// values. Generally seen when constructing an offset that is greater than the
/// allowed range.
///
/// This error is returned by [`Offset::try_from`].
///
/// [`Offset::try_from`]: super::Offset::try_from
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct TzOutOfRangeError {
    _private: (),
}

impl fmt::Display for TzOutOfRangeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        f.write_str("utc_offset out of range")
    }
}

impl error::Error for TzOutOfRangeError {}

impl TzOutOfRangeError {
    // This error is not to be constructed outside of this crate.
    pub(crate) const fn new() -> Self {
        Self { _private: () }
    }
}

impl From<TryFromFloatSecsError> for TzOutOfRangeError {
    fn from(_err: TryFromFloatSecsError) -> Self {
        Self::new()
    }
}

/// Error that indicates a given operation has resulted in an integer overflow.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct IntOverflowError {
    _private: (),
}

impl fmt::Display for IntOverflowError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        f.write_str("out of int range")
    }
}

impl error::Error for IntOverflowError {}

impl IntOverflowError {
    // This error is not to be constructed outside of this crate.
    pub(crate) const fn new() -> Self {
        Self { _private: () }
    }
}

impl From<TryFromIntError> for TimeError {
    fn from(_: TryFromIntError) -> Self {
        Self::IntOverflowError(IntOverflowError::new())
    }
}