From c2790437d3bfe00896e3b04aee247209542c5a07 Mon Sep 17 00:00:00 2001 From: x-hgg-x <39058530+x-hgg-x@users.noreply.github.com> Date: Sat, 14 Sep 2024 14:43:51 +0200 Subject: [PATCH] Rework error types --- examples/settings.rs | 2 +- examples/statics.rs | 3 +- examples/time.rs | 2 +- src/datetime/find.rs | 33 +++--- src/datetime/mod.rs | 114 ++++++++++---------- src/error/datetime.rs | 37 +++++++ src/error/mod.rs | 236 +++++++---------------------------------- src/error/parse.rs | 153 ++++++++++++++++++++++++++ src/error/timezone.rs | 97 +++++++++++++++++ src/lib.rs | 9 +- src/parse/tz_file.rs | 25 ++--- src/parse/tz_string.rs | 31 +++--- src/parse/utils.rs | 2 +- src/timezone/mod.rs | 116 ++++++++++---------- src/timezone/rule.rs | 49 ++++----- src/utils/const_fns.rs | 8 +- 16 files changed, 525 insertions(+), 392 deletions(-) create mode 100644 src/error/datetime.rs create mode 100644 src/error/parse.rs create mode 100644 src/error/timezone.rs diff --git a/examples/settings.rs b/examples/settings.rs index 72e71e3..4686be3 100644 --- a/examples/settings.rs +++ b/examples/settings.rs @@ -1,4 +1,4 @@ -fn main() -> tz::Result<()> { +fn main() -> Result<(), tz::Error> { #[cfg(feature = "std")] { use tz::TimeZoneSettings; diff --git a/examples/statics.rs b/examples/statics.rs index b4e7e74..9708c97 100644 --- a/examples/statics.rs +++ b/examples/statics.rs @@ -1,7 +1,8 @@ use tz::datetime::{DateTime, UtcDateTime}; +use tz::error::TzError; use tz::timezone::{AlternateTime, LeapSecond, LocalTimeType, MonthWeekDay, RuleDay, TimeZoneRef, Transition, TransitionRule}; -fn main() -> tz::Result<()> { +fn main() -> Result<(), TzError> { macro_rules! unwrap { ($x:expr) => { match $x { diff --git a/examples/time.rs b/examples/time.rs index 54bac42..c61aca1 100644 --- a/examples/time.rs +++ b/examples/time.rs @@ -1,4 +1,4 @@ -fn main() -> tz::Result<()> { +fn main() -> Result<(), tz::Error> { #[cfg(feature = "std")] { use tz::{DateTime, LocalTimeType, TimeZone, UtcDateTime}; diff --git a/src/datetime/find.rs b/src/datetime/find.rs index 35e9dc0..c8bb2cc 100644 --- a/src/datetime/find.rs +++ b/src/datetime/find.rs @@ -1,9 +1,8 @@ //! Types related to the [`DateTime::find`] method. use crate::datetime::{check_date_time_inputs, unix_time, DateTime, UtcDateTime}; -use crate::error::OutOfRangeError; +use crate::error::TzError; use crate::timezone::{TimeZoneRef, TransitionRule}; -use crate::Result; #[cfg(feature = "alloc")] use alloc::vec::Vec; @@ -180,7 +179,7 @@ pub(super) fn find_date_time( second: u8, nanoseconds: u32, time_zone_ref: TimeZoneRef<'_>, -) -> Result<()> { +) -> Result<(), TzError> { let transitions = time_zone_ref.transitions(); let local_time_types = time_zone_ref.local_time_types(); let extra_rule = time_zone_ref.extra_rule(); @@ -200,16 +199,16 @@ pub(super) fn find_date_time( if !transitions.is_empty() { let mut last_cached_time = None; - let mut get_time = |local_time_type_index: usize| { + let mut get_time = |local_time_type_index: usize| -> Result<_, TzError> { match last_cached_time { - Some((index, value)) if index == local_time_type_index => Result::Ok(value), + Some((index, value)) if index == local_time_type_index => Ok(value), _ => { // Overflow is not possible let unix_time = utc_unix_time - local_time_types[local_time_type_index].ut_offset() as i64; let unix_leap_time = time_zone_ref.unix_time_to_unix_leap_time(unix_time)?; last_cached_time = Some((local_time_type_index, (unix_time, unix_leap_time))); - Result::Ok((unix_time, unix_leap_time)) + Ok((unix_time, unix_leap_time)) } } }; @@ -282,7 +281,7 @@ pub(super) fn find_date_time( // Check if the year is valid for the following computations if !(i32::MIN + 2..=i32::MAX - 2).contains(&year) { - return Err(OutOfRangeError("out of range date time").into()); + return Err(TzError::OutOfRange); } // Check DST start/end Unix times for previous/current/next years to support for transition day times outside of [0h, 24h] range. @@ -380,9 +379,9 @@ mod tests { unique: Option<[usize; 2]>, earlier: Option<[usize; 2]>, later: Option<[usize; 2]>, - ) -> Result<()> { + ) -> Result<(), TzError> { let new_date_time = |(year, month, month_day, hour, minute, second, ut_offset)| { - Result::Ok(DateTime::new(year, month, month_day, hour, minute, second, 0, LocalTimeType::with_ut_offset(ut_offset)?)?) + DateTime::new(year, month, month_day, hour, minute, second, 0, LocalTimeType::with_ut_offset(ut_offset)?) }; let (year, month, month_day, hour, minute, second) = searched; @@ -425,7 +424,7 @@ mod tests { } #[test] - fn test_find_date_time_fixed() -> Result<()> { + fn test_find_date_time_fixed() -> Result<(), TzError> { let local_time_type = LocalTimeType::with_ut_offset(3600)?; let results = &[Check::Normal([3600])]; @@ -445,7 +444,7 @@ mod tests { } #[test] - fn test_find_date_time_no_offset() -> Result<()> { + fn test_find_date_time_no_offset() -> Result<(), TzError> { let local_time_types = [ LocalTimeType::new(0, false, Some(b"STD1"))?, LocalTimeType::new(0, true, Some(b"DST1"))?, @@ -469,7 +468,7 @@ mod tests { let time_zone_ref = time_zone.as_ref(); - let find_unique_local_time_type = |year, month, month_day, hour, minute, second, nanoseconds| { + let find_unique_local_time_type = |year, month, month_day, hour, minute, second, nanoseconds| -> Result<_, TzError> { let mut found_date_time_list = FoundDateTimeList::default(); find_date_time(&mut found_date_time_list, year, month, month_day, hour, minute, second, nanoseconds, time_zone_ref)?; @@ -482,7 +481,7 @@ mod tests { let datetime_2 = found_date_time_list_ref_mut.unique().unwrap(); assert_eq!(datetime_1, datetime_2); - Result::Ok(*datetime_1.local_time_type()) + Ok(*datetime_1.local_time_type()) }; assert_eq!(local_time_types[0], find_unique_local_time_type(1970, 1, 1, 0, 30, 0, 0)?); @@ -495,7 +494,7 @@ mod tests { } #[test] - fn test_find_date_time_extra_rule_only() -> Result<()> { + fn test_find_date_time_extra_rule_only() -> Result<(), TzError> { let time_zone = TimeZone::new( vec![], vec![LocalTimeType::utc(), LocalTimeType::with_ut_offset(3600)?], @@ -535,7 +534,7 @@ mod tests { } #[test] - fn test_find_date_time_transitions_only() -> Result<()> { + fn test_find_date_time_transitions_only() -> Result<(), TzError> { let time_zone = TimeZone::new( vec![ Transition::new(0, 0), @@ -591,7 +590,7 @@ mod tests { } #[test] - fn test_find_date_time_transitions_with_extra_rule() -> Result<()> { + fn test_find_date_time_transitions_with_extra_rule() -> Result<(), TzError> { let time_zone = TimeZone::new( vec![Transition::new(0, 0), Transition::new(3600, 1), Transition::new(7200, 0), Transition::new(10800, 2)], vec![LocalTimeType::utc(), LocalTimeType::with_ut_offset(i32::MAX)?, LocalTimeType::with_ut_offset(3600)?], @@ -655,7 +654,7 @@ mod tests { } #[test] - fn test_find_date_time_ref_mut() -> Result<()> { + fn test_find_date_time_ref_mut() -> Result<(), TzError> { let transitions = &[Transition::new(3600, 1), Transition::new(86400, 0), Transition::new(i64::MAX, 0)]; let local_time_types = &[LocalTimeType::new(0, false, Some(b"STD"))?, LocalTimeType::new(3600, true, Some(b"DST"))?]; let time_zone_ref = TimeZoneRef::new(transitions, local_time_types, &[], &None)?; diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index d2419e7..e39e111 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -10,7 +10,8 @@ pub use find::{FoundDateTimeKind, FoundDateTimeListRefMut}; use crate::constants::*; use crate::datetime::find::find_date_time; -use crate::error::{DateTimeError, FindLocalTimeTypeError, OutOfRangeError, ProjectDateTimeError, TzError}; +use crate::error::datetime::DateTimeError; +use crate::error::TzError; use crate::timezone::{LocalTimeType, TimeZoneRef}; use crate::utils::{min, try_into_i32, try_into_i64}; @@ -49,11 +50,11 @@ impl UtcDateTime { const MAX_UNIX_TIME: i64 = 67767976233532799; /// Check if the UTC date time associated to a Unix time in seconds is valid - const fn check_unix_time(unix_time: i64) -> Result<(), DateTimeError> { + const fn check_unix_time(unix_time: i64) -> Result<(), TzError> { if Self::MIN_UNIX_TIME <= unix_time && unix_time <= Self::MAX_UNIX_TIME { Ok(()) } else { - Err(DateTimeError("out of range date time")) + Err(TzError::OutOfRange) } } @@ -69,24 +70,24 @@ impl UtcDateTime { /// * `second`: Seconds in `[0, 60]`, with a possible leap second /// * `nanoseconds`: Nanoseconds in `[0, 999_999_999]` /// - pub const fn new(year: i32, month: u8, month_day: u8, hour: u8, minute: u8, second: u8, nanoseconds: u32) -> Result { + pub const fn new(year: i32, month: u8, month_day: u8, hour: u8, minute: u8, second: u8, nanoseconds: u32) -> Result { // Exclude the maximum possible UTC date time with a leap second if year == i32::MAX && month == 12 && month_day == 31 && hour == 23 && minute == 59 && second == 60 { - return Err(DateTimeError("out of range date time")); + return Err(TzError::OutOfRange); } if let Err(error) = check_date_time_inputs(year, month, month_day, hour, minute, second, nanoseconds) { - return Err(error); + return Err(TzError::DateTime(error)); } Ok(Self { year, month, month_day, hour, minute, second, nanoseconds }) } /// Construct a UTC date time from a Unix time in seconds and nanoseconds - pub const fn from_timespec(unix_time: i64, nanoseconds: u32) -> Result { + pub const fn from_timespec(unix_time: i64, nanoseconds: u32) -> Result { let seconds = match unix_time.checked_sub(UNIX_OFFSET_SECS) { Some(seconds) => seconds, - None => return Err(OutOfRangeError("out of range operation")), + None => return Err(TzError::OutOfRange), }; let mut remaining_days = seconds / SECONDS_PER_DAY; @@ -146,7 +147,7 @@ impl UtcDateTime { } /// Construct a UTC date time from total nanoseconds since Unix epoch (`1970-01-01T00:00:00Z`) - pub const fn from_total_nanoseconds(total_nanoseconds: i128) -> Result { + pub const fn from_total_nanoseconds(total_nanoseconds: i128) -> Result { match total_nanoseconds_to_timespec(total_nanoseconds) { Ok((unix_time, nanoseconds)) => Self::from_timespec(unix_time, nanoseconds), Err(error) => Err(error), @@ -162,14 +163,14 @@ impl UtcDateTime { /// /// Leap seconds are not preserved. /// - pub const fn project(&self, time_zone_ref: TimeZoneRef<'_>) -> Result { + pub const fn project(&self, time_zone_ref: TimeZoneRef<'_>) -> Result { DateTime::from_timespec(self.unix_time(), self.nanoseconds, time_zone_ref) } /// Returns the current UTC date time #[cfg(feature = "std")] pub fn now() -> Result { - Ok(Self::from_total_nanoseconds(crate::utils::current_total_nanoseconds())?) + Self::from_total_nanoseconds(crate::utils::current_total_nanoseconds()) } } @@ -239,9 +240,9 @@ impl DateTime { second: u8, nanoseconds: u32, local_time_type: LocalTimeType, - ) -> Result { + ) -> Result { if let Err(error) = check_date_time_inputs(year, month, month_day, hour, minute, second, nanoseconds) { - return Err(error); + return Err(TzError::DateTime(error)); } // Overflow is not possible @@ -354,15 +355,15 @@ impl DateTime { } /// Construct a date time from a Unix time in seconds with nanoseconds and a local time type - pub const fn from_timespec_and_local(unix_time: i64, nanoseconds: u32, local_time_type: LocalTimeType) -> Result { + pub const fn from_timespec_and_local(unix_time: i64, nanoseconds: u32, local_time_type: LocalTimeType) -> Result { let unix_time_with_offset = match unix_time.checked_add(local_time_type.ut_offset() as i64) { Some(unix_time_with_offset) => unix_time_with_offset, - None => return Err(ProjectDateTimeError("out of range date time")), + None => return Err(TzError::OutOfRange), }; let utc_date_time_with_offset = match UtcDateTime::from_timespec(unix_time_with_offset, nanoseconds) { Ok(utc_date_time_with_offset) => utc_date_time_with_offset, - Err(OutOfRangeError(error)) => return Err(ProjectDateTimeError(error)), + Err(error) => return Err(error), }; let UtcDateTime { year, month, month_day, hour, minute, second, nanoseconds } = utc_date_time_with_offset; @@ -370,28 +371,28 @@ impl DateTime { } /// Construct a date time from a Unix time in seconds with nanoseconds and a time zone - pub const fn from_timespec(unix_time: i64, nanoseconds: u32, time_zone_ref: TimeZoneRef<'_>) -> Result { + pub const fn from_timespec(unix_time: i64, nanoseconds: u32, time_zone_ref: TimeZoneRef<'_>) -> Result { let local_time_type = match time_zone_ref.find_local_time_type(unix_time) { Ok(&local_time_type) => local_time_type, - Err(FindLocalTimeTypeError(error)) => return Err(ProjectDateTimeError(error)), + Err(error) => return Err(error), }; Self::from_timespec_and_local(unix_time, nanoseconds, local_time_type) } /// Construct a date time from total nanoseconds since Unix epoch (`1970-01-01T00:00:00Z`) and a local time type - pub const fn from_total_nanoseconds_and_local(total_nanoseconds: i128, local_time_type: LocalTimeType) -> Result { + pub const fn from_total_nanoseconds_and_local(total_nanoseconds: i128, local_time_type: LocalTimeType) -> Result { match total_nanoseconds_to_timespec(total_nanoseconds) { Ok((unix_time, nanoseconds)) => Self::from_timespec_and_local(unix_time, nanoseconds, local_time_type), - Err(OutOfRangeError(error)) => Err(ProjectDateTimeError(error)), + Err(error) => Err(error), } } /// Construct a date time from total nanoseconds since Unix epoch (`1970-01-01T00:00:00Z`) and a time zone - pub const fn from_total_nanoseconds(total_nanoseconds: i128, time_zone_ref: TimeZoneRef<'_>) -> Result { + pub const fn from_total_nanoseconds(total_nanoseconds: i128, time_zone_ref: TimeZoneRef<'_>) -> Result { match total_nanoseconds_to_timespec(total_nanoseconds) { Ok((unix_time, nanoseconds)) => Self::from_timespec(unix_time, nanoseconds, time_zone_ref), - Err(OutOfRangeError(error)) => Err(ProjectDateTimeError(error)), + Err(error) => Err(error), } } @@ -399,7 +400,7 @@ impl DateTime { /// /// Leap seconds are not preserved. /// - pub const fn project(&self, time_zone_ref: TimeZoneRef<'_>) -> Result { + pub const fn project(&self, time_zone_ref: TimeZoneRef<'_>) -> Result { Self::from_timespec(self.unix_time, self.nanoseconds, time_zone_ref) } @@ -407,7 +408,7 @@ impl DateTime { #[cfg(feature = "std")] pub fn now(time_zone_ref: TimeZoneRef<'_>) -> Result { let now = crate::utils::current_total_nanoseconds(); - Ok(Self::from_total_nanoseconds(now, time_zone_ref)?) + Self::from_total_nanoseconds(now, time_zone_ref) } } @@ -610,7 +611,7 @@ const fn nanoseconds_since_unix_epoch(unix_time: i64, nanoseconds: u32) -> i128 /// * `nanoseconds`: Nanoseconds in `[0, 999_999_999]` /// #[inline] -const fn total_nanoseconds_to_timespec(total_nanoseconds: i128) -> Result<(i64, u32), OutOfRangeError> { +const fn total_nanoseconds_to_timespec(total_nanoseconds: i128) -> Result<(i64, u32), TzError> { let unix_time = match try_into_i64(total_nanoseconds.div_euclid(NANOSECONDS_PER_SECOND as i128)) { Ok(unix_time) => unix_time, Err(error) => return Err(error), @@ -635,22 +636,22 @@ const fn total_nanoseconds_to_timespec(total_nanoseconds: i128) -> Result<(i64, /// const fn check_date_time_inputs(year: i32, month: u8, month_day: u8, hour: u8, minute: u8, second: u8, nanoseconds: u32) -> Result<(), DateTimeError> { if !(1 <= month && month <= 12) { - return Err(DateTimeError("invalid month")); + return Err(DateTimeError::InvalidMonth); } if !(1 <= month_day && month_day <= 31) { - return Err(DateTimeError("invalid month day")); + return Err(DateTimeError::InvalidMonthDay); } if hour > 23 { - return Err(DateTimeError("invalid hour")); + return Err(DateTimeError::InvalidHour); } if minute > 59 { - return Err(DateTimeError("invalid minute")); + return Err(DateTimeError::InvalidMinute); } if second > 60 { - return Err(DateTimeError("invalid second")); + return Err(DateTimeError::InvalidSecond); } if nanoseconds >= NANOSECONDS_PER_SECOND { - return Err(DateTimeError("invalid nanoseconds")); + return Err(DateTimeError::InvalidNanoseconds); } let leap = is_leap_year(year) as i64; @@ -661,7 +662,7 @@ const fn check_date_time_inputs(year: i32, month: u8, month_day: u8, hour: u8, m } if month_day as i64 > days_in_month { - return Err(DateTimeError("invalid month day")); + return Err(DateTimeError::InvalidMonthDay); } Ok(()) @@ -720,7 +721,6 @@ fn format_date_time( #[cfg(test)] mod tests { use super::*; - use crate::Result; #[cfg(feature = "alloc")] use crate::timezone::TimeZone; @@ -740,7 +740,7 @@ mod tests { #[cfg(feature = "alloc")] #[test] - fn test_date_time() -> Result<()> { + fn test_date_time() -> Result<(), TzError> { let time_zone_utc = TimeZone::utc(); let utc = LocalTimeType::utc(); @@ -875,7 +875,7 @@ mod tests { #[cfg(feature = "alloc")] #[test] - fn test_date_time_leap_seconds() -> Result<()> { + fn test_date_time_leap_seconds() -> Result<(), TzError> { let utc_date_time = UtcDateTime::new(1972, 6, 30, 23, 59, 60, 1000)?; assert_eq!(UtcDateTime::from_timespec(utc_date_time.unix_time(), 1000)?, UtcDateTime::new(1972, 7, 1, 0, 0, 0, 1000)?); @@ -901,7 +901,7 @@ mod tests { #[cfg(feature = "alloc")] #[test] - fn test_date_time_partial_eq_partial_ord() -> Result<()> { + fn test_date_time_partial_eq_partial_ord() -> Result<(), TzError> { let time_zone_utc = TimeZone::utc(); let time_zone_cet = TimeZone::fixed(3600)?; let time_zone_eet = TimeZone::fixed(7200)?; @@ -968,7 +968,7 @@ mod tests { } #[test] - fn test_utc_date_time_ord() -> Result<()> { + fn test_utc_date_time_ord() -> Result<(), TzError> { let utc_date_time_1 = UtcDateTime::new(1972, 6, 30, 23, 59, 59, 1000)?; let utc_date_time_2 = UtcDateTime::new(1972, 6, 30, 23, 59, 60, 1000)?; let utc_date_time_3 = UtcDateTime::new(1972, 7, 1, 0, 0, 0, 1000)?; @@ -1013,7 +1013,7 @@ mod tests { #[cfg(feature = "alloc")] #[test] - fn test_date_time_format() -> Result<()> { + fn test_date_time_format() -> Result<(), TzError> { use alloc::string::ToString; let time_zones = [ @@ -1069,37 +1069,37 @@ mod tests { #[cfg(feature = "alloc")] #[test] - fn test_date_time_overflow() -> Result<()> { + fn test_date_time_overflow() -> Result<(), TzError> { assert!(UtcDateTime::new(i32::MIN, 1, 1, 0, 0, 0, 0).is_ok()); assert!(UtcDateTime::new(i32::MAX, 12, 31, 23, 59, 59, 0).is_ok()); assert!(DateTime::new(i32::MIN, 1, 1, 0, 0, 0, 0, LocalTimeType::utc()).is_ok()); assert!(DateTime::new(i32::MAX, 12, 31, 23, 59, 59, 0, LocalTimeType::utc()).is_ok()); - assert!(matches!(DateTime::new(i32::MIN, 1, 1, 0, 0, 0, 0, LocalTimeType::with_ut_offset(1)?), Err(DateTimeError(_)))); - assert!(matches!(DateTime::new(i32::MAX, 12, 31, 23, 59, 59, 0, LocalTimeType::with_ut_offset(-1)?), Err(DateTimeError(_)))); + assert!(matches!(DateTime::new(i32::MIN, 1, 1, 0, 0, 0, 0, LocalTimeType::with_ut_offset(1)?), Err(TzError::OutOfRange))); + assert!(matches!(DateTime::new(i32::MAX, 12, 31, 23, 59, 59, 0, LocalTimeType::with_ut_offset(-1)?), Err(TzError::OutOfRange))); - assert!(matches!(UtcDateTime::new(i32::MAX, 12, 31, 23, 59, 60, 0), Err(DateTimeError(_)))); - assert!(matches!(DateTime::new(i32::MAX, 12, 31, 23, 59, 60, 0, LocalTimeType::utc()), Err(DateTimeError(_)))); + assert!(matches!(UtcDateTime::new(i32::MAX, 12, 31, 23, 59, 60, 0), Err(TzError::OutOfRange))); + assert!(matches!(DateTime::new(i32::MAX, 12, 31, 23, 59, 60, 0, LocalTimeType::utc()), Err(TzError::OutOfRange))); assert!(DateTime::new(i32::MAX, 12, 31, 23, 59, 60, 0, LocalTimeType::with_ut_offset(1)?).is_ok()); assert!(UtcDateTime::from_timespec(UtcDateTime::MIN_UNIX_TIME, 0).is_ok()); assert!(UtcDateTime::from_timespec(UtcDateTime::MAX_UNIX_TIME, 0).is_ok()); - assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MIN_UNIX_TIME - 1, 0), Err(OutOfRangeError(_)))); - assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MAX_UNIX_TIME + 1, 0), Err(OutOfRangeError(_)))); + assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MIN_UNIX_TIME - 1, 0), Err(TzError::OutOfRange))); + assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MAX_UNIX_TIME + 1, 0), Err(TzError::OutOfRange))); - assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MIN_UNIX_TIME, 0)?.project(TimeZone::fixed(-1)?.as_ref()), Err(ProjectDateTimeError(_)))); - assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MAX_UNIX_TIME, 0)?.project(TimeZone::fixed(1)?.as_ref()), Err(ProjectDateTimeError(_)))); + assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MIN_UNIX_TIME, 0)?.project(TimeZone::fixed(-1)?.as_ref()), Err(TzError::OutOfRange))); + assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MAX_UNIX_TIME, 0)?.project(TimeZone::fixed(1)?.as_ref()), Err(TzError::OutOfRange))); - assert!(matches!(UtcDateTime::from_timespec(i64::MIN, 0), Err(OutOfRangeError(_)))); - assert!(matches!(UtcDateTime::from_timespec(i64::MAX, 0), Err(OutOfRangeError(_)))); + assert!(matches!(UtcDateTime::from_timespec(i64::MIN, 0), Err(TzError::OutOfRange))); + assert!(matches!(UtcDateTime::from_timespec(i64::MAX, 0), Err(TzError::OutOfRange))); assert!(DateTime::from_timespec(UtcDateTime::MIN_UNIX_TIME, 0, TimeZone::fixed(0)?.as_ref()).is_ok()); assert!(DateTime::from_timespec(UtcDateTime::MAX_UNIX_TIME, 0, TimeZone::fixed(0)?.as_ref()).is_ok()); - assert!(matches!(DateTime::from_timespec(i64::MIN, 0, TimeZone::fixed(-1)?.as_ref()), Err(ProjectDateTimeError(_)))); - assert!(matches!(DateTime::from_timespec(i64::MAX, 0, TimeZone::fixed(1)?.as_ref()), Err(ProjectDateTimeError(_)))); + assert!(matches!(DateTime::from_timespec(i64::MIN, 0, TimeZone::fixed(-1)?.as_ref()), Err(TzError::OutOfRange))); + assert!(matches!(DateTime::from_timespec(i64::MAX, 0, TimeZone::fixed(1)?.as_ref()), Err(TzError::OutOfRange))); Ok(()) } @@ -1170,14 +1170,14 @@ mod tests { } #[test] - fn test_total_nanoseconds_to_timespec() -> Result<()> { + fn test_total_nanoseconds_to_timespec() -> Result<(), TzError> { assert!(matches!(total_nanoseconds_to_timespec(1_000_001_000), Ok((1, 1000)))); assert!(matches!(total_nanoseconds_to_timespec(1000), Ok((0, 1000)))); assert!(matches!(total_nanoseconds_to_timespec(-999_999_000), Ok((-1, 1000)))); assert!(matches!(total_nanoseconds_to_timespec(-1_999_999_000), Ok((-2, 1000)))); - assert!(matches!(total_nanoseconds_to_timespec(i128::MAX), Err(OutOfRangeError(_)))); - assert!(matches!(total_nanoseconds_to_timespec(i128::MIN), Err(OutOfRangeError(_)))); + assert!(matches!(total_nanoseconds_to_timespec(i128::MAX), Err(TzError::OutOfRange))); + assert!(matches!(total_nanoseconds_to_timespec(i128::MIN), Err(TzError::OutOfRange))); let min_total_nanoseconds = -9223372036854775808000000000; let max_total_nanoseconds = 9223372036854775807999999999; @@ -1185,14 +1185,14 @@ mod tests { assert!(matches!(total_nanoseconds_to_timespec(min_total_nanoseconds), Ok((i64::MIN, 0)))); assert!(matches!(total_nanoseconds_to_timespec(max_total_nanoseconds), Ok((i64::MAX, 999999999)))); - assert!(matches!(total_nanoseconds_to_timespec(min_total_nanoseconds - 1), Err(OutOfRangeError(_)))); - assert!(matches!(total_nanoseconds_to_timespec(max_total_nanoseconds + 1), Err(OutOfRangeError(_)))); + assert!(matches!(total_nanoseconds_to_timespec(min_total_nanoseconds - 1), Err(TzError::OutOfRange))); + assert!(matches!(total_nanoseconds_to_timespec(max_total_nanoseconds + 1), Err(TzError::OutOfRange))); Ok(()) } #[test] - fn test_const() -> Result<()> { + fn test_const() -> Result<(), TzError> { use crate::timezone::{AlternateTime, LeapSecond, MonthWeekDay, RuleDay, Transition, TransitionRule}; macro_rules! unwrap { diff --git a/src/error/datetime.rs b/src/error/datetime.rs new file mode 100644 index 0000000..1e25dfb --- /dev/null +++ b/src/error/datetime.rs @@ -0,0 +1,37 @@ +//! Date time error types. + +use core::error::Error; +use core::fmt; + +/// Date time error +#[non_exhaustive] +#[derive(Debug)] +pub enum DateTimeError { + /// Invalid month + InvalidMonth, + /// Invalid month day + InvalidMonthDay, + /// Invalid hour + InvalidHour, + /// Invalid minute + InvalidMinute, + /// Invalid second + InvalidSecond, + /// Invalid nanoseconds + InvalidNanoseconds, +} + +impl fmt::Display for DateTimeError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + Self::InvalidMonth => f.write_str("invalid month"), + Self::InvalidMonthDay => f.write_str("invalid month day"), + Self::InvalidHour => f.write_str("invalid hour"), + Self::InvalidMinute => f.write_str("invalid minute"), + Self::InvalidSecond => f.write_str("invalid second"), + Self::InvalidNanoseconds => f.write_str("invalid nanoseconds"), + } + } +} + +impl Error for DateTimeError {} diff --git a/src/error/mod.rs b/src/error/mod.rs index c78f16b..ea65458 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -1,186 +1,53 @@ //! Error types. -use core::error::Error; -use core::fmt; +pub mod datetime; +pub mod timezone; -/// Parsing error types. #[cfg(feature = "alloc")] -mod parse { - use super::*; - - use alloc::boxed::Box; - use core::num::ParseIntError; - use core::str::Utf8Error; - - /// Parse data error - #[non_exhaustive] - #[derive(Debug)] - pub enum ParseDataError { - /// Unexpected end of data - UnexpectedEof, - /// Invalid data - InvalidData, - } - - impl fmt::Display for ParseDataError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match self { - Self::UnexpectedEof => f.write_str("unexpected end of data"), - Self::InvalidData => f.write_str("invalid data"), - } - } - } - - impl Error for ParseDataError {} +pub mod parse; - /// Unified error type for parsing a TZ string - #[non_exhaustive] - #[derive(Debug)] - pub enum TzStringError { - /// UTF-8 error - Utf8(Utf8Error), - /// Integer parsing error - ParseInt(ParseIntError), - /// Parse data error - ParseData(ParseDataError), - /// Invalid TZ string - InvalidTzString(&'static str), - /// Unsupported TZ string - UnsupportedTzString(&'static str), - } - - impl fmt::Display for TzStringError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match self { - Self::Utf8(error) => error.fmt(f), - Self::ParseInt(error) => error.fmt(f), - Self::ParseData(error) => error.fmt(f), - Self::InvalidTzString(error) => write!(f, "invalid TZ string: {error}"), - Self::UnsupportedTzString(error) => write!(f, "unsupported TZ string: {error}"), - } - } - } - - impl Error for TzStringError {} - - impl From for TzStringError { - fn from(error: Utf8Error) -> Self { - Self::Utf8(error) - } - } - - impl From for TzStringError { - fn from(error: ParseIntError) -> Self { - Self::ParseInt(error) - } - } - - impl From for TzStringError { - fn from(error: ParseDataError) -> Self { - Self::ParseData(error) - } - } - - /// Unified error type for parsing a TZif file - #[non_exhaustive] - #[derive(Debug)] - pub enum TzFileError { - /// File was not found - FileNotFound, - /// UTF-8 error - Utf8(Utf8Error), - /// Parse data error - ParseData(ParseDataError), - /// I/O error - Io(Box), - /// Unified error for parsing a TZ string - TzString(TzStringError), - /// Invalid TZif file - InvalidTzFile(&'static str), - /// Unsupported TZif file - UnsupportedTzFile(&'static str), - } - - impl fmt::Display for TzFileError { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match self { - Self::FileNotFound => f.write_str("file was not found"), - Self::Utf8(error) => error.fmt(f), - Self::ParseData(error) => error.fmt(f), - Self::Io(error) => error.fmt(f), - Self::TzString(error) => error.fmt(f), - Self::InvalidTzFile(error) => write!(f, "invalid TZ file: {error}"), - Self::UnsupportedTzFile(error) => write!(f, "unsupported TZ file: {error}"), - } - } - } - - impl Error for TzFileError {} - - impl From for TzFileError { - fn from(error: Utf8Error) -> Self { - Self::Utf8(error) - } - } +use datetime::DateTimeError; +use timezone::{LocalTimeTypeError, TimeZoneError, TransitionRuleError}; - impl From for TzFileError { - fn from(error: ParseDataError) -> Self { - Self::ParseData(error) - } - } - - impl From for TzFileError { - fn from(error: TzStringError) -> Self { - Self::TzString(error) - } - } -} - -#[doc(inline)] #[cfg(feature = "alloc")] -pub use parse::{ParseDataError, TzFileError, TzStringError}; +use parse::{TzFileError, TzStringError}; -/// Create a new error type -macro_rules! create_error { - (#[$doc:meta], $name:ident) => { - #[$doc] - #[derive(Debug)] - pub struct $name( - /// Error description - pub &'static str, - ); +use core::error; +use core::fmt; - impl fmt::Display for $name { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - self.0.fmt(f) - } - } +#[cfg(feature = "alloc")] +use alloc::boxed::Box; - impl Error for $name {} - }; +/// Unified error type for everything in the crate +#[non_exhaustive] +#[derive(Debug)] +pub enum Error { + /// I/O error + #[cfg(feature = "alloc")] + Io(Box), + /// Unified error type for every non I/O error in the crate + Tz(TzError), } -create_error!(#[doc = "Out of range error"], OutOfRangeError); -create_error!(#[doc = "Local time type error"], LocalTimeTypeError); -create_error!(#[doc = "Transition rule error"], TransitionRuleError); -create_error!(#[doc = "Time zone error"], TimeZoneError); -create_error!(#[doc = "Date time error"], DateTimeError); -create_error!(#[doc = "Local time type search error"], FindLocalTimeTypeError); -create_error!(#[doc = "Date time projection error"], ProjectDateTimeError); - -impl From for ProjectDateTimeError { - fn from(error: OutOfRangeError) -> Self { - Self(error.0) +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + #[cfg(feature = "alloc")] + Self::Io(error) => error.fmt(f), + Self::Tz(error) => error.fmt(f), + } } } -impl From for ProjectDateTimeError { - fn from(error: FindLocalTimeTypeError) -> Self { - Self(error.0) +impl error::Error for Error {} + +impl> From for Error { + fn from(error: T) -> Self { + Self::Tz(error.into()) } } -/// Unified error type for everything in the crate +/// Unified error type for every non I/O error in the crate #[non_exhaustive] #[derive(Debug)] pub enum TzError { @@ -190,8 +57,6 @@ pub enum TzError { /// Unified error for parsing a TZ string #[cfg(feature = "alloc")] TzString(TzStringError), - /// Out of range error - OutOfRange(OutOfRangeError), /// Local time type error LocalTimeType(LocalTimeTypeError), /// Transition rule error @@ -200,31 +65,30 @@ pub enum TzError { TimeZone(TimeZoneError), /// Date time error DateTime(DateTimeError), - /// Local time type search error - FindLocalTimeType(FindLocalTimeTypeError), - /// Date time projection error - ProjectDateTime(ProjectDateTimeError), + /// Out of range operation + OutOfRange, + /// No available local time type + NoAvailableLocalTimeType, } impl fmt::Display for TzError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { #[cfg(feature = "alloc")] - Self::TzFile(error) => error.fmt(f), + Self::TzFile(error) => write!(f, "invalid TZ file: {error}"), #[cfg(feature = "alloc")] - Self::TzString(error) => error.fmt(f), - Self::OutOfRange(error) => error.fmt(f), + Self::TzString(error) => write!(f, "invalid TZ string: {error}"), Self::LocalTimeType(error) => write!(f, "invalid local time type: {error}"), Self::TransitionRule(error) => write!(f, "invalid transition rule: {error}"), Self::TimeZone(error) => write!(f, "invalid time zone: {error}"), Self::DateTime(error) => write!(f, "invalid date time: {error}"), - Self::FindLocalTimeType(error) => error.fmt(f), - Self::ProjectDateTime(error) => error.fmt(f), + Self::OutOfRange => f.write_str("out of range operation"), + Self::NoAvailableLocalTimeType => write!(f, "no local time type is available for the specified timestamp"), } } } -impl Error for TzError {} +impl error::Error for TzError {} #[cfg(feature = "alloc")] impl From for TzError { @@ -240,12 +104,6 @@ impl From for TzError { } } -impl From for TzError { - fn from(error: OutOfRangeError) -> Self { - Self::OutOfRange(error) - } -} - impl From for TzError { fn from(error: LocalTimeTypeError) -> Self { Self::LocalTimeType(error) @@ -269,15 +127,3 @@ impl From for TzError { Self::DateTime(error) } } - -impl From for TzError { - fn from(error: FindLocalTimeTypeError) -> Self { - Self::FindLocalTimeType(error) - } -} - -impl From for TzError { - fn from(error: ProjectDateTimeError) -> Self { - Self::ProjectDateTime(error) - } -} diff --git a/src/error/parse.rs b/src/error/parse.rs new file mode 100644 index 0000000..0d08b6e --- /dev/null +++ b/src/error/parse.rs @@ -0,0 +1,153 @@ +//! Parsing error types. + +use core::error::Error; +use core::fmt; +use core::num::ParseIntError; +use core::str::Utf8Error; + +/// Parse data error +#[non_exhaustive] +#[derive(Debug)] +pub enum ParseDataError { + /// Unexpected end of data + UnexpectedEof, + /// Invalid data + InvalidData, +} + +impl fmt::Display for ParseDataError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + Self::UnexpectedEof => f.write_str("unexpected end of data"), + Self::InvalidData => f.write_str("invalid data"), + } + } +} + +impl Error for ParseDataError {} + +/// Unified error type for parsing a TZ string +#[non_exhaustive] +#[derive(Debug)] +pub enum TzStringError { + /// UTF-8 error + Utf8(Utf8Error), + /// Integer parsing error + ParseInt(ParseIntError), + /// Parse data error + ParseData(ParseDataError), + /// Invalid offset hour + InvalidOffsetHour, + /// Invalid offset minute + InvalidOffsetMinute, + /// Invalid offset second + InvalidOffsetSecond, + /// Invalid day time hour + InvalidDayTimeHour, + /// Invalid day time minute + InvalidDayTimeMinute, + /// Invalid day time second + InvalidDayTimeSecond, + /// Missing DST start and end rules + MissingDstStartEndRules, + /// Remaining data was found after parsing TZ string + RemainingData, + /// Empty TZ string + Empty, +} + +impl fmt::Display for TzStringError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + Self::Utf8(error) => error.fmt(f), + Self::ParseInt(error) => error.fmt(f), + Self::ParseData(error) => error.fmt(f), + Self::InvalidOffsetHour => f.write_str("invalid offset hour"), + Self::InvalidOffsetMinute => f.write_str("invalid offset minute"), + Self::InvalidOffsetSecond => f.write_str("invalid offset second"), + Self::InvalidDayTimeHour => f.write_str("invalid day time hour"), + Self::InvalidDayTimeMinute => f.write_str("invalid day time minute"), + Self::InvalidDayTimeSecond => f.write_str("invalid day time second"), + Self::MissingDstStartEndRules => f.write_str("missing DST start and end rules"), + Self::RemainingData => f.write_str("remaining data after parsing TZ string"), + Self::Empty => f.write_str("empty TZ string"), + } + } +} + +impl Error for TzStringError {} + +impl From for TzStringError { + fn from(error: Utf8Error) -> Self { + Self::Utf8(error) + } +} + +impl From for TzStringError { + fn from(error: ParseIntError) -> Self { + Self::ParseInt(error) + } +} + +impl From for TzStringError { + fn from(error: ParseDataError) -> Self { + Self::ParseData(error) + } +} + +/// Unified error type for parsing a TZif file +#[non_exhaustive] +#[derive(Debug)] +pub enum TzFileError { + /// UTF-8 error + Utf8(Utf8Error), + /// Parse data error + ParseData(ParseDataError), + /// Invalid magic number + InvalidMagicNumber, + /// Unsupported TZif version + UnsupportedTzFileVersion, + /// Invalid header + InvalidHeader, + /// Invalid footer + InvalidFooter, + /// Invalid DST indicator + InvalidDstIndicator, + /// Invalid time zone designation char index + InvalidTimeZoneDesignationCharIndex, + /// Invalid couple of standard/wall and UT/local indicators + InvalidStdWallUtLocal, + /// Remaining data after the end of a TZif v1 data block + RemainingDataV1, +} + +impl fmt::Display for TzFileError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + Self::Utf8(error) => error.fmt(f), + Self::ParseData(error) => error.fmt(f), + Self::InvalidMagicNumber => f.write_str("invalid magic number"), + Self::UnsupportedTzFileVersion => write!(f, "unsupported TZ file version"), + Self::InvalidHeader => f.write_str("invalid header"), + Self::InvalidFooter => f.write_str("invalid footer"), + Self::InvalidDstIndicator => f.write_str("invalid DST indicator"), + Self::InvalidTimeZoneDesignationCharIndex => f.write_str("invalid time zone designation char index"), + Self::InvalidStdWallUtLocal => f.write_str("invalid couple of standard/wall and UT/local indicators"), + Self::RemainingDataV1 => f.write_str("remaining data after the end of a TZif v1 data block"), + } + } +} + +impl Error for TzFileError {} + +impl From for TzFileError { + fn from(error: Utf8Error) -> Self { + Self::Utf8(error) + } +} + +impl From for TzFileError { + fn from(error: ParseDataError) -> Self { + Self::ParseData(error) + } +} diff --git a/src/error/timezone.rs b/src/error/timezone.rs new file mode 100644 index 0000000..8fe4fba --- /dev/null +++ b/src/error/timezone.rs @@ -0,0 +1,97 @@ +//! Time zone error types. + +use core::error::Error; +use core::fmt; + +/// Local time type error +#[non_exhaustive] +#[derive(Debug)] +pub enum LocalTimeTypeError { + /// Invalid time zone designation length + InvalidTimeZoneDesignationLength, + /// Invalid characters in time zone designation + InvalidTimeZoneDesignationChar, + /// Invalid UTC offset + InvalidUtcOffset, +} + +impl fmt::Display for LocalTimeTypeError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + Self::InvalidTimeZoneDesignationLength => f.write_str("time zone designation must have between 3 and 7 characters"), + Self::InvalidTimeZoneDesignationChar => f.write_str("invalid characters in time zone designation"), + Self::InvalidUtcOffset => f.write_str("invalid UTC offset"), + } + } +} + +impl Error for LocalTimeTypeError {} + +/// Transition rule error +#[non_exhaustive] +#[derive(Debug)] +pub enum TransitionRuleError { + /// Invalid rule day julian day + InvalidRuleDayJulianDay, + /// Invalid rule day month + InvalidRuleDayMonth, + /// Invalid rule day week + InvalidRuleDayWeek, + /// Invalid rule day week day + InvalidRuleDayWeekDay, + /// Invalid standard time UTC offset + InvalidStdUtcOffset, + /// Invalid Daylight Saving Time UTC offset + InvalidDstUtcOffset, + /// Invalid DST start or end time + InvalidDstStartEndTime, + /// Inconsistent DST transition rules from one year to another + InconsistentRule, +} + +impl fmt::Display for TransitionRuleError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + Self::InvalidRuleDayJulianDay => f.write_str("invalid rule day julian day"), + Self::InvalidRuleDayMonth => f.write_str("invalid rule day month"), + Self::InvalidRuleDayWeek => f.write_str("invalid rule day week"), + Self::InvalidRuleDayWeekDay => f.write_str("invalid rule day week day"), + Self::InvalidStdUtcOffset => f.write_str("invalid standard time UTC offset"), + Self::InvalidDstUtcOffset => f.write_str("invalid Daylight Saving Time UTC offset"), + Self::InvalidDstStartEndTime => f.write_str("invalid DST start or end time"), + Self::InconsistentRule => f.write_str("DST transition rules are not consistent from one year to another"), + } + } +} + +impl Error for TransitionRuleError {} + +/// Time zone error +#[non_exhaustive] +#[derive(Debug)] +pub enum TimeZoneError { + /// No local time type + NoLocalTimeType, + /// Invalid local time type index + InvalidLocalTimeTypeIndex, + /// Invalid transition + InvalidTransition, + /// Invalid leap second + InvalidLeapSecond, + /// Inconsistent extra transition rule relative to the last transition + InconsistentExtraRule, +} + +impl fmt::Display for TimeZoneError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + Self::NoLocalTimeType => f.write_str("list of local time types must not be empty"), + Self::InvalidLocalTimeTypeIndex => f.write_str("invalid local time type index"), + Self::InvalidTransition => f.write_str("invalid transition"), + Self::InvalidLeapSecond => f.write_str("invalid leap second"), + Self::InconsistentExtraRule => f.write_str("extra transition rule is inconsistent with the last transition"), + } + } +} + +impl Error for TimeZoneError {} diff --git a/src/lib.rs b/src/lib.rs index 2078003..15e5832 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ //! ## Time zone //! //! ```rust -//! # fn main() -> Result<(), tz::TzError> { +//! # fn main() -> Result<(), tz::Error> { //! # #[cfg(feature = "std")] { //! use tz::TimeZone; //! @@ -59,7 +59,7 @@ //! ## Date time //! //! ```rust -//! # fn main() -> Result<(), tz::TzError> { +//! # fn main() -> Result<(), tz::Error> { //! # #[cfg(feature = "std")] { //! use tz::{DateTime, LocalTimeType, TimeZone, UtcDateTime}; //! @@ -213,7 +213,7 @@ pub mod timezone; pub use datetime::{DateTime, UtcDateTime}; #[doc(inline)] -pub use error::TzError; +pub use error::{Error, TzError}; #[doc(inline)] pub use timezone::{LocalTimeType, TimeZoneRef}; @@ -221,6 +221,3 @@ pub use timezone::{LocalTimeType, TimeZoneRef}; #[doc(inline)] #[cfg(feature = "alloc")] pub use timezone::{TimeZone, TimeZoneSettings}; - -/// Alias for [`core::result::Result`] with the crate unified error -pub type Result = core::result::Result; diff --git a/src/parse/tz_file.rs b/src/parse/tz_file.rs index 742dc2c..185bb4b 100644 --- a/src/parse/tz_file.rs +++ b/src/parse/tz_file.rs @@ -1,6 +1,7 @@ //! Functions used for parsing a TZif file. -use crate::error::{TzError, TzFileError}; +use crate::error::parse::TzFileError; +use crate::error::TzError; use crate::parse::tz_string::parse_posix_tz; use crate::parse::utils::{read_chunk_exact, read_exact, Cursor}; use crate::timezone::{LeapSecond, LocalTimeType, TimeZone, Transition, TransitionRule}; @@ -43,14 +44,14 @@ struct Header { fn parse_header(cursor: &mut Cursor<'_>) -> Result { let magic = read_exact(cursor, 4)?; if magic != *b"TZif" { - return Err(TzFileError::InvalidTzFile("invalid magic number")); + return Err(TzFileError::InvalidMagicNumber); } let version = match read_exact(cursor, 1)? { [0x00] => Version::V1, [0x32] => Version::V2, [0x33] => Version::V3, - _ => return Err(TzFileError::UnsupportedTzFile("unsupported TZif version")), + _ => return Err(TzFileError::UnsupportedTzFileVersion), }; read_exact(cursor, 15)?; @@ -63,7 +64,7 @@ fn parse_header(cursor: &mut Cursor<'_>) -> Result { let char_count = u32::from_be_bytes(*read_chunk_exact(cursor)?); if !(type_count != 0 && char_count != 0 && (ut_local_count == 0 || ut_local_count == type_count) && (std_wall_count == 0 || std_wall_count == type_count)) { - return Err(TzFileError::InvalidTzFile("invalid header")); + return Err(TzFileError::InvalidHeader); } Ok(Header { @@ -81,12 +82,12 @@ fn parse_header(cursor: &mut Cursor<'_>) -> Result { fn parse_footer(footer: &[u8], use_string_extensions: bool) -> Result, TzError> { let footer = str::from_utf8(footer).map_err(TzFileError::from)?; if !(footer.starts_with('\n') && footer.ends_with('\n')) { - return Err(TzFileError::InvalidTzFile("invalid footer").into()); + return Err(TzError::TzFile(TzFileError::InvalidFooter)); } let tz_string = footer.trim_matches(|c: char| c.is_ascii_whitespace()); if tz_string.starts_with(':') || tz_string.contains('\0') { - return Err(TzFileError::InvalidTzFile("invalid footer").into()); + return Err(TzError::TzFile(TzFileError::InvalidFooter)); } if !tz_string.is_empty() { @@ -173,16 +174,16 @@ where let is_dst = match d4 { 0 => false, 1 => true, - _ => return Err(TzFileError::InvalidTzFile("invalid DST indicator").into()), + _ => return Err(TzError::TzFile(TzFileError::InvalidDstIndicator)), }; let char_index = d5 as usize; if char_index >= header.char_count { - return Err(TzFileError::InvalidTzFile("invalid time zone designation char index").into()); + return Err(TzError::TzFile(TzFileError::InvalidTimeZoneDesignationCharIndex)); } let time_zone_designation = match self.time_zone_designations[char_index..].iter().position(|&c| c == b'\0') { - None => return Err(TzFileError::InvalidTzFile("invalid time zone designation char index").into()), + None => return Err(TzError::TzFile(TzFileError::InvalidTimeZoneDesignationCharIndex)), Some(position) => { let time_zone_designation = &self.time_zone_designations[char_index..char_index + position]; @@ -211,13 +212,13 @@ where let ut_locals_iter = self.ut_locals.iter().copied().chain(iter::repeat(0)); for (std_wall, ut_local) in std_walls_iter.zip(ut_locals_iter).take(header.type_count) { if !matches!((std_wall, ut_local), (0, 0) | (1, 0) | (1, 1)) { - return Err(TzFileError::InvalidTzFile("invalid couple of standard/wall and UT/local indicators").into()); + return Err(TzError::TzFile(TzFileError::InvalidStdWallUtLocal)); } } let extra_rule = footer.and_then(|footer| parse_footer(footer, header.version == Version::V3).transpose()).transpose()?; - Ok(TimeZone::new(transitions, local_time_types, leap_seconds, extra_rule)?) + TimeZone::new(transitions, local_time_types, leap_seconds, extra_rule) } } @@ -232,7 +233,7 @@ pub(crate) fn parse_tz_file(bytes: &[u8]) -> Result { let data_blocks = read_data_blocks::<4>(&mut cursor, &header)?; if !cursor.is_empty() { - return Err(TzFileError::InvalidTzFile("remaining data after end of TZif v1 data block").into()); + return Err(TzError::TzFile(TzFileError::RemainingDataV1)); } Ok(data_blocks.parse(&header, None)?) diff --git a/src/parse/tz_string.rs b/src/parse/tz_string.rs index 9898ed6..028e8d8 100644 --- a/src/parse/tz_string.rs +++ b/src/parse/tz_string.rs @@ -1,6 +1,7 @@ //! Functions used for parsing a TZ string. -use crate::error::{ParseDataError, TzError, TzStringError}; +use crate::error::parse::{ParseDataError, TzStringError}; +use crate::error::TzError; use crate::parse::utils::{read_exact, read_optional_tag, read_tag, read_until, read_while, Cursor}; use crate::timezone::{AlternateTime, Julian0WithLeap, Julian1WithoutLeap, LocalTimeType, MonthWeekDay, RuleDay, TransitionRule}; @@ -68,13 +69,13 @@ fn parse_offset(cursor: &mut Cursor<'_>) -> Result { let (sign, hour, minute, second) = parse_signed_hhmmss(cursor)?; if !(0..=24).contains(&hour) { - return Err(TzStringError::InvalidTzString("invalid offset hour")); + return Err(TzStringError::InvalidOffsetHour); } if !(0..=59).contains(&minute) { - return Err(TzStringError::InvalidTzString("invalid offset minute")); + return Err(TzStringError::InvalidOffsetMinute); } if !(0..=59).contains(&second) { - return Err(TzStringError::InvalidTzString("invalid offset second")); + return Err(TzStringError::InvalidOffsetSecond); } Ok(sign * (hour * 3600 + minute * 60 + second)) @@ -111,13 +112,13 @@ fn parse_rule_time(cursor: &mut Cursor<'_>) -> Result { let (hour, minute, second) = parse_hhmmss(cursor)?; if !(0..=24).contains(&hour) { - return Err(TzStringError::InvalidTzString("invalid day time hour")); + return Err(TzStringError::InvalidDayTimeHour); } if !(0..=59).contains(&minute) { - return Err(TzStringError::InvalidTzString("invalid day time minute")); + return Err(TzStringError::InvalidDayTimeMinute); } if !(0..=59).contains(&second) { - return Err(TzStringError::InvalidTzString("invalid day time second")); + return Err(TzStringError::InvalidDayTimeSecond); } Ok(hour * 3600 + minute * 60 + second) @@ -128,13 +129,13 @@ fn parse_rule_time_extended(cursor: &mut Cursor<'_>) -> Result R let dst_offset = match cursor.first() { Some(&b',') => std_offset - 3600, Some(_) => parse_offset(&mut cursor)?, - None => return Err(TzStringError::UnsupportedTzString("DST start and end rules must be provided").into()), + None => return Err(TzError::TzString(TzStringError::MissingDstStartEndRules)), }; if cursor.is_empty() { - return Err(TzStringError::UnsupportedTzString("DST start and end rules must be provided").into()); + return Err(TzError::TzString(TzStringError::MissingDstStartEndRules)); } map_err(read_tag(&mut cursor, b","))?; @@ -190,7 +191,7 @@ pub(crate) fn parse_posix_tz(tz_string: &[u8], use_string_extensions: bool) -> R let (dst_end, dst_end_time) = parse_rule_block(&mut cursor, use_string_extensions)?; if !cursor.is_empty() { - return Err(TzStringError::InvalidTzString("remaining data after parsing TZ string").into()); + return Err(TzError::TzString(TzStringError::RemainingData)); } Ok(TransitionRule::Alternate(AlternateTime::new( @@ -365,8 +366,8 @@ mod tests { #[test] fn test_error() -> Result<(), TzError> { - assert!(matches!(parse_posix_tz(b"IST-1GMT0", false), Err(TzError::TzString(TzStringError::UnsupportedTzString(_))))); - assert!(matches!(parse_posix_tz(b"EET-2EEST", false), Err(TzError::TzString(TzStringError::UnsupportedTzString(_))))); + assert!(matches!(parse_posix_tz(b"IST-1GMT0", false), Err(TzError::TzString(TzStringError::MissingDstStartEndRules)))); + assert!(matches!(parse_posix_tz(b"EET-2EEST", false), Err(TzError::TzString(TzStringError::MissingDstStartEndRules)))); Ok(()) } diff --git a/src/parse/utils.rs b/src/parse/utils.rs index a60dea4..a207272 100644 --- a/src/parse/utils.rs +++ b/src/parse/utils.rs @@ -1,6 +1,6 @@ //! Some useful functions. -use crate::error::ParseDataError; +use crate::error::parse::ParseDataError; /// Cursor type alias pub(super) type Cursor<'a> = &'a [u8]; diff --git a/src/timezone/mod.rs b/src/timezone/mod.rs index 31340a5..a615248 100644 --- a/src/timezone/mod.rs +++ b/src/timezone/mod.rs @@ -5,12 +5,13 @@ mod rule; #[doc(inline)] pub use rule::{AlternateTime, Julian0WithLeap, Julian1WithoutLeap, MonthWeekDay, RuleDay, TransitionRule}; -use crate::error::{FindLocalTimeTypeError, LocalTimeTypeError, OutOfRangeError, TimeZoneError}; +use crate::error::timezone::{LocalTimeTypeError, TimeZoneError}; +use crate::error::TzError; use crate::utils::{binary_search_leap_seconds, binary_search_transitions}; #[cfg(feature = "alloc")] use crate::{ - error::{TzError, TzFileError, TzStringError}, + error::parse::TzStringError, parse::{parse_posix_tz, parse_tz_file}, }; @@ -91,7 +92,7 @@ impl TzAsciiStr { let len = input.len(); if !(3 <= len && len <= 7) { - return Err(LocalTimeTypeError("time zone designation must have between 3 and 7 characters")); + return Err(LocalTimeTypeError::InvalidTimeZoneDesignationLength); } let mut bytes = [0; 8]; @@ -102,7 +103,7 @@ impl TzAsciiStr { let b = input[i]; if !matches!(b, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-') { - return Err(LocalTimeTypeError("invalid characters in time zone designation")); + return Err(LocalTimeTypeError::InvalidTimeZoneDesignationChar); } bytes[i + 1] = b; @@ -163,7 +164,7 @@ impl LocalTimeType { /// Construct a local time type pub const fn new(ut_offset: i32, is_dst: bool, time_zone_designation: Option<&[u8]>) -> Result { if ut_offset == i32::MIN { - return Err(LocalTimeTypeError("invalid UTC offset")); + return Err(LocalTimeTypeError::InvalidUtcOffset); } let time_zone_designation = match time_zone_designation { @@ -187,7 +188,7 @@ impl LocalTimeType { #[inline] pub const fn with_ut_offset(ut_offset: i32) -> Result { if ut_offset == i32::MIN { - return Err(LocalTimeTypeError("invalid UTC offset")); + return Err(LocalTimeTypeError::InvalidUtcOffset); } Ok(Self { ut_offset, is_dst: false, time_zone_designation: None }) @@ -247,7 +248,7 @@ impl<'a> TimeZoneRef<'a> { local_time_types: &'a [LocalTimeType], leap_seconds: &'a [LeapSecond], extra_rule: &'a Option, - ) -> Result { + ) -> Result { let time_zone_ref = Self::new_unchecked(transitions, local_time_types, leap_seconds, extra_rule); if let Err(error) = time_zone_ref.check_inputs() { @@ -288,7 +289,7 @@ impl<'a> TimeZoneRef<'a> { } /// Find the local time type associated to the time zone at the specified Unix time in seconds - pub const fn find_local_time_type(&self, unix_time: i64) -> Result<&'a LocalTimeType, FindLocalTimeTypeError> { + pub const fn find_local_time_type(&self, unix_time: i64) -> Result<&'a LocalTimeType, TzError> { let extra_rule = match self.transitions { [] => match self.extra_rule { Some(extra_rule) => extra_rule, @@ -297,13 +298,13 @@ impl<'a> TimeZoneRef<'a> { [.., last_transition] => { let unix_leap_time = match self.unix_time_to_unix_leap_time(unix_time) { Ok(unix_leap_time) => unix_leap_time, - Err(OutOfRangeError(error)) => return Err(FindLocalTimeTypeError(error)), + Err(error) => return Err(error), }; if unix_leap_time >= last_transition.unix_leap_time { match self.extra_rule { Some(extra_rule) => extra_rule, - None => return Err(FindLocalTimeTypeError("no local time type is available for the specified timestamp")), + None => return Err(TzError::NoAvailableLocalTimeType), } } else { let index = match binary_search_transitions(self.transitions, unix_leap_time) { @@ -317,10 +318,7 @@ impl<'a> TimeZoneRef<'a> { } }; - match extra_rule.find_local_time_type(unix_time) { - Ok(local_time_type) => Ok(local_time_type), - Err(OutOfRangeError(error)) => Err(FindLocalTimeTypeError(error)), - } + extra_rule.find_local_time_type(unix_time) } /// Construct a reference to a time zone @@ -335,24 +333,24 @@ impl<'a> TimeZoneRef<'a> { } /// Check time zone inputs - const fn check_inputs(&self) -> Result<(), TimeZoneError> { + const fn check_inputs(&self) -> Result<(), TzError> { use crate::constants::*; // Check local time types let local_time_types_size = self.local_time_types.len(); if local_time_types_size == 0 { - return Err(TimeZoneError("list of local time types must not be empty")); + return Err(TzError::TimeZone(TimeZoneError::NoLocalTimeType)); } // Check transitions let mut i_transition = 0; while i_transition < self.transitions.len() { if self.transitions[i_transition].local_time_type_index >= local_time_types_size { - return Err(TimeZoneError("invalid local time type index")); + return Err(TzError::TimeZone(TimeZoneError::InvalidLocalTimeTypeIndex)); } if i_transition + 1 < self.transitions.len() && self.transitions[i_transition].unix_leap_time >= self.transitions[i_transition + 1].unix_leap_time { - return Err(TimeZoneError("invalid transition")); + return Err(TzError::TimeZone(TimeZoneError::InvalidTransition)); } i_transition += 1; @@ -360,7 +358,7 @@ impl<'a> TimeZoneRef<'a> { // Check leap seconds if !(self.leap_seconds.is_empty() || self.leap_seconds[0].unix_leap_time >= 0 && self.leap_seconds[0].correction.saturating_abs() == 1) { - return Err(TimeZoneError("invalid leap second")); + return Err(TzError::TimeZone(TimeZoneError::InvalidLeapSecond)); } let min_interval = SECONDS_PER_28_DAYS - 1; @@ -375,7 +373,7 @@ impl<'a> TimeZoneRef<'a> { let abs_diff_correction = x1.correction.saturating_sub(x0.correction).saturating_abs(); if !(diff_unix_leap_time >= min_interval && abs_diff_correction == 1) { - return Err(TimeZoneError("invalid leap second")); + return Err(TzError::TimeZone(TimeZoneError::InvalidLeapSecond)); } } i_leap_second += 1; @@ -387,16 +385,16 @@ impl<'a> TimeZoneRef<'a> { let unix_time = match self.unix_leap_time_to_unix_time(last_transition.unix_leap_time) { Ok(unix_time) => unix_time, - Err(OutOfRangeError(error)) => return Err(TimeZoneError(error)), + Err(error) => return Err(error), }; let rule_local_time_type = match extra_rule.find_local_time_type(unix_time) { Ok(rule_local_time_type) => rule_local_time_type, - Err(OutOfRangeError(error)) => return Err(TimeZoneError(error)), + Err(error) => return Err(error), }; if !last_local_time_type.equal(rule_local_time_type) { - return Err(TimeZoneError("extra transition rule is inconsistent with the last transition")); + return Err(TzError::TimeZone(TimeZoneError::InconsistentExtraRule)); } } @@ -404,7 +402,7 @@ impl<'a> TimeZoneRef<'a> { } /// Convert Unix time to Unix leap time, from the list of leap seconds in a time zone - pub(crate) const fn unix_time_to_unix_leap_time(&self, unix_time: i64) -> Result { + pub(crate) const fn unix_time_to_unix_leap_time(&self, unix_time: i64) -> Result { let mut unix_leap_time = unix_time; let mut i = 0; @@ -417,7 +415,7 @@ impl<'a> TimeZoneRef<'a> { unix_leap_time = match unix_time.checked_add(leap_second.correction as i64) { Some(unix_leap_time) => unix_leap_time, - None => return Err(OutOfRangeError("out of range operation")), + None => return Err(TzError::OutOfRange), }; i += 1; @@ -427,9 +425,9 @@ impl<'a> TimeZoneRef<'a> { } /// Convert Unix leap time to Unix time, from the list of leap seconds in a time zone - pub(crate) const fn unix_leap_time_to_unix_time(&self, unix_leap_time: i64) -> Result { + pub(crate) const fn unix_leap_time_to_unix_time(&self, unix_leap_time: i64) -> Result { if unix_leap_time == i64::MIN { - return Err(OutOfRangeError("out of range operation")); + return Err(TzError::OutOfRange); } let index = match binary_search_leap_seconds(self.leap_seconds, unix_leap_time - 1) { @@ -441,7 +439,7 @@ impl<'a> TimeZoneRef<'a> { match unix_leap_time.checked_sub(correction as i64) { Some(unix_time) => Ok(unix_time), - None => Err(OutOfRangeError("out of range operation")), + None => Err(TzError::OutOfRange), } } } @@ -468,7 +466,7 @@ impl TimeZone { local_time_types: Vec, leap_seconds: Vec, extra_rule: Option, - ) -> Result { + ) -> Result { TimeZoneRef::new_unchecked(&transitions, &local_time_types, &leap_seconds, &extra_rule).check_inputs()?; Ok(Self { transitions, local_time_types, leap_seconds, extra_rule }) } @@ -492,7 +490,7 @@ impl TimeZone { } /// Find the local time type associated to the time zone at the specified Unix time in seconds - pub fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, FindLocalTimeTypeError> { + pub fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, TzError> { self.as_ref().find_local_time_type(unix_time) } @@ -506,20 +504,20 @@ impl TimeZone { /// This method in not supported on non-UNIX platforms, and returns the UTC time zone instead. /// #[cfg(feature = "std")] - pub fn local() -> Result { + pub fn local() -> Result { TimeZoneSettings::DEFAULT.parse_local() } /// Construct a time zone from a POSIX TZ string, as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html). #[cfg(feature = "std")] - pub fn from_posix_tz(tz_string: &str) -> Result { + pub fn from_posix_tz(tz_string: &str) -> Result { TimeZoneSettings::DEFAULT.parse_posix_tz(tz_string) } /// Find the current local time type associated to the time zone #[cfg(feature = "std")] pub fn find_current_local_time_type(&self) -> Result<&LocalTimeType, TzError> { - Ok(self.find_local_time_type(crate::utils::current_unix_time())?) + self.find_local_time_type(crate::utils::current_unix_time()) } } @@ -559,7 +557,7 @@ impl<'a> TimeZoneSettings<'a> { /// /// This method in not supported on non-UNIX platforms, and returns the UTC time zone instead. /// - pub fn parse_local(&self) -> Result { + pub fn parse_local(&self) -> Result { #[cfg(not(unix))] let local_time_zone = TimeZone::utc(); @@ -571,22 +569,22 @@ impl<'a> TimeZoneSettings<'a> { /// Construct a time zone from a POSIX TZ string using current settings, /// as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html). - pub fn parse_posix_tz(&self, tz_string: &str) -> Result { + pub fn parse_posix_tz(&self, tz_string: &str) -> Result { if tz_string.is_empty() { - return Err(TzError::TzString(TzStringError::InvalidTzString("empty TZ string"))); + return Err(TzStringError::Empty.into()); } if tz_string == "localtime" { - return parse_tz_file(&(self.read_file_fn)("/etc/localtime").map_err(TzFileError::Io)?); + return Ok(parse_tz_file(&(self.read_file_fn)("/etc/localtime").map_err(crate::Error::Io)?)?); } let mut chars = tz_string.chars(); if chars.next() == Some(':') { - return parse_tz_file(&self.read_tz_file(chars.as_str())?); + return Ok(parse_tz_file(&self.read_tz_file(chars.as_str())?)?); } match self.read_tz_file(tz_string) { - Ok(bytes) => parse_tz_file(&bytes), + Ok(bytes) => Ok(parse_tz_file(&bytes)?), Err(_) => { let tz_string = tz_string.trim_matches(|c: char| c.is_ascii_whitespace()); @@ -604,8 +602,8 @@ impl<'a> TimeZoneSettings<'a> { } /// Read the TZif file corresponding to a TZ string using current settings - fn read_tz_file(&self, tz_string: &str) -> Result, TzFileError> { - let read_file_fn = |path: &str| (self.read_file_fn)(path).map_err(TzFileError::Io); + fn read_tz_file(&self, tz_string: &str) -> Result, crate::Error> { + let read_file_fn = |path: &str| (self.read_file_fn)(path).map_err(crate::Error::Io); // Don't check system timezone directories on non-UNIX platforms #[cfg(not(unix))] @@ -615,7 +613,10 @@ impl<'a> TimeZoneSettings<'a> { if tz_string.starts_with('/') { Ok(read_file_fn(tz_string)?) } else { - self.directories.iter().find_map(|folder| read_file_fn(&format!("{folder}/{tz_string}")).ok()).ok_or(TzFileError::FileNotFound) + self.directories + .iter() + .find_map(|folder| read_file_fn(&format!("{folder}/{tz_string}")).ok()) + .ok_or_else(|| crate::Error::Io("file was not found".into())) } } } @@ -623,30 +624,29 @@ impl<'a> TimeZoneSettings<'a> { #[cfg(test)] mod tests { use super::*; - use crate::Result; #[test] - fn test_tz_ascii_str() -> Result<()> { - assert!(matches!(TzAsciiStr::new(b""), Err(LocalTimeTypeError(_)))); - assert!(matches!(TzAsciiStr::new(b"1"), Err(LocalTimeTypeError(_)))); - assert!(matches!(TzAsciiStr::new(b"12"), Err(LocalTimeTypeError(_)))); + fn test_tz_ascii_str() -> Result<(), TzError> { + assert!(matches!(TzAsciiStr::new(b""), Err(LocalTimeTypeError::InvalidTimeZoneDesignationLength))); + assert!(matches!(TzAsciiStr::new(b"1"), Err(LocalTimeTypeError::InvalidTimeZoneDesignationLength))); + assert!(matches!(TzAsciiStr::new(b"12"), Err(LocalTimeTypeError::InvalidTimeZoneDesignationLength))); assert_eq!(TzAsciiStr::new(b"123")?.as_bytes(), b"123"); assert_eq!(TzAsciiStr::new(b"1234")?.as_bytes(), b"1234"); assert_eq!(TzAsciiStr::new(b"12345")?.as_bytes(), b"12345"); assert_eq!(TzAsciiStr::new(b"123456")?.as_bytes(), b"123456"); assert_eq!(TzAsciiStr::new(b"1234567")?.as_bytes(), b"1234567"); - assert!(matches!(TzAsciiStr::new(b"12345678"), Err(LocalTimeTypeError(_)))); - assert!(matches!(TzAsciiStr::new(b"123456789"), Err(LocalTimeTypeError(_)))); - assert!(matches!(TzAsciiStr::new(b"1234567890"), Err(LocalTimeTypeError(_)))); + assert!(matches!(TzAsciiStr::new(b"12345678"), Err(LocalTimeTypeError::InvalidTimeZoneDesignationLength))); + assert!(matches!(TzAsciiStr::new(b"123456789"), Err(LocalTimeTypeError::InvalidTimeZoneDesignationLength))); + assert!(matches!(TzAsciiStr::new(b"1234567890"), Err(LocalTimeTypeError::InvalidTimeZoneDesignationLength))); - assert!(matches!(TzAsciiStr::new(b"123\0\0\0"), Err(LocalTimeTypeError(_)))); + assert!(matches!(TzAsciiStr::new(b"123\0\0\0"), Err(LocalTimeTypeError::InvalidTimeZoneDesignationChar))); Ok(()) } #[cfg(feature = "alloc")] #[test] - fn test_time_zone() -> Result<()> { + fn test_time_zone() -> Result<(), TzError> { let utc = LocalTimeType::utc(); let cet = LocalTimeType::with_ut_offset(3600)?; @@ -662,7 +662,7 @@ mod tests { assert_eq!(*time_zone_2.find_local_time_type(0)?, cet); assert_eq!(*time_zone_3.find_local_time_type(-1)?, utc); - assert!(matches!(time_zone_3.find_local_time_type(0), Err(FindLocalTimeTypeError(_)))); + assert!(matches!(time_zone_3.find_local_time_type(0), Err(TzError::NoAvailableLocalTimeType))); assert_eq!(*time_zone_4.find_local_time_type(-1)?, utc); assert_eq!(*time_zone_4.find_local_time_type(0)?, cet); @@ -675,7 +675,7 @@ mod tests { #[cfg(feature = "std")] #[test] - fn test_time_zone_from_posix_tz() -> Result<()> { + fn test_time_zone_from_posix_tz() -> Result<(), crate::Error> { #[cfg(unix)] { let time_zone_local = TimeZone::local()?; @@ -687,7 +687,7 @@ mod tests { assert_eq!(time_zone_local, time_zone_local_2); assert_eq!(time_zone_local, time_zone_local_3); - assert!(matches!(time_zone_local.find_current_local_time_type(), Ok(_) | Err(TzError::FindLocalTimeType(_)))); + assert!(matches!(time_zone_local.find_current_local_time_type(), Ok(_) | Err(TzError::NoAvailableLocalTimeType))); let time_zone_utc = TimeZone::from_posix_tz("UTC")?; assert_eq!(time_zone_utc.find_local_time_type(0)?.ut_offset(), 0); @@ -701,7 +701,7 @@ mod tests { #[cfg(feature = "alloc")] #[test] - fn test_leap_seconds() -> Result<()> { + fn test_leap_seconds() -> Result<(), TzError> { let time_zone = TimeZone::new( vec![], vec![LocalTimeType::new(0, false, Some(b"UTC"))?], @@ -753,7 +753,7 @@ mod tests { #[cfg(feature = "alloc")] #[test] - fn test_leap_seconds_overflow() -> Result<()> { + fn test_leap_seconds_overflow() -> Result<(), TzError> { let time_zone_err = TimeZone::new( vec![Transition::new(i64::MIN, 0)], vec![LocalTimeType::utc()], @@ -763,7 +763,7 @@ mod tests { assert!(time_zone_err.is_err()); let time_zone = TimeZone::new(vec![Transition::new(i64::MAX, 0)], vec![LocalTimeType::utc()], vec![LeapSecond::new(0, 1)], None)?; - assert!(matches!(time_zone.find_local_time_type(i64::MAX), Err(FindLocalTimeTypeError(_)))); + assert!(matches!(time_zone.find_local_time_type(i64::MAX), Err(TzError::OutOfRange))); Ok(()) } diff --git a/src/timezone/rule.rs b/src/timezone/rule.rs index d4ed19e..2736fec 100644 --- a/src/timezone/rule.rs +++ b/src/timezone/rule.rs @@ -2,7 +2,8 @@ use crate::constants::*; use crate::datetime::{days_since_unix_epoch, is_leap_year, UtcDateTime}; -use crate::error::{OutOfRangeError, TransitionRuleError}; +use crate::error::timezone::TransitionRuleError; +use crate::error::TzError; use crate::timezone::LocalTimeType; use crate::utils::{binary_search_i64, cmp}; @@ -43,7 +44,7 @@ impl Julian1WithoutLeap { #[inline] pub const fn new(julian_day_1: u16) -> Result { if !(1 <= julian_day_1 && julian_day_1 <= 365) { - return Err(TransitionRuleError("invalid rule day julian day")); + return Err(TransitionRuleError::InvalidRuleDayJulianDay); } Ok(Self(julian_day_1)) @@ -98,7 +99,7 @@ impl Julian0WithLeap { #[inline] pub const fn new(julian_day_0: u16) -> Result { if julian_day_0 > 365 { - return Err(TransitionRuleError("invalid rule day julian day")); + return Err(TransitionRuleError::InvalidRuleDayJulianDay); } Ok(Self(julian_day_0)) @@ -163,15 +164,15 @@ impl MonthWeekDay { #[inline] pub const fn new(month: u8, week: u8, week_day: u8) -> Result { if !(1 <= month && month <= 12) { - return Err(TransitionRuleError("invalid rule day month")); + return Err(TransitionRuleError::InvalidRuleDayMonth); } if !(1 <= week && week <= 5) { - return Err(TransitionRuleError("invalid rule day week")); + return Err(TransitionRuleError::InvalidRuleDayWeek); } if week_day > 6 { - return Err(TransitionRuleError("invalid rule day week day")); + return Err(TransitionRuleError::InvalidRuleDayWeekDay); } Ok(Self { month, week, week_day }) @@ -333,21 +334,21 @@ impl AlternateTime { // Limit UTC offset to POSIX-required range if !(-25 * SECONDS_PER_HOUR < std_ut_offset && std_ut_offset < 26 * SECONDS_PER_HOUR) { - return Err(TransitionRuleError("invalid standard time UTC offset")); + return Err(TransitionRuleError::InvalidStdUtcOffset); } if !(-25 * SECONDS_PER_HOUR < dst_ut_offset && dst_ut_offset < 26 * SECONDS_PER_HOUR) { - return Err(TransitionRuleError("invalid Daylight Saving Time UTC offset")); + return Err(TransitionRuleError::InvalidDstUtcOffset); } // Overflow is not possible if !((dst_start_time as i64).abs() < SECONDS_PER_WEEK && (dst_end_time as i64).abs() < SECONDS_PER_WEEK) { - return Err(TransitionRuleError("invalid DST start or end time")); + return Err(TransitionRuleError::InvalidDstStartEndTime); } // Check DST transition rules consistency if !check_dst_transition_rules_consistency(&std, &dst, dst_start, dst_start_time, dst_end, dst_end_time) { - return Err(TransitionRuleError("DST transition rules are not consistent from one year to another")); + return Err(TransitionRuleError::InconsistentRule); } Ok(Self { std, dst, dst_start, dst_start_time, dst_end, dst_end_time }) @@ -390,7 +391,7 @@ impl AlternateTime { } /// Find the local time type associated to the alternate transition rule at the specified Unix time in seconds - const fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, OutOfRangeError> { + const fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, TzError> { // Overflow is not possible let dst_start_time_in_utc = self.dst_start_time as i64 - self.std.ut_offset as i64; let dst_end_time_in_utc = self.dst_end_time as i64 - self.dst.ut_offset as i64; @@ -402,7 +403,7 @@ impl AlternateTime { // Check if the current year is valid for the following computations if !(i32::MIN + 2 <= current_year && current_year <= i32::MAX - 2) { - return Err(OutOfRangeError("out of range date time")); + return Err(TzError::OutOfRange); } let current_year_dst_start_unix_time = self.dst_start.unix_time(current_year, dst_start_time_in_utc); @@ -475,7 +476,7 @@ pub enum TransitionRule { impl TransitionRule { /// Find the local time type associated to the transition rule at the specified Unix time in seconds - pub(super) const fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, OutOfRangeError> { + pub(super) const fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, TzError> { match self { Self::Fixed(local_time_type) => Ok(local_time_type), Self::Alternate(alternate_time) => alternate_time.find_local_time_type(unix_time), @@ -706,10 +707,10 @@ const fn check_two_month_week_days(month_week_day_1: MonthWeekDay, utc_day_time_ #[cfg(test)] mod tests { use super::*; - use crate::Result; + use crate::TzError; #[test] - fn test_compute_check_infos() -> Result<()> { + fn test_compute_check_infos() -> Result<(), TzError> { let check_julian = |check_infos: JulianDayCheckInfos, start_normal, end_normal, start_leap, end_leap| { assert_eq!(check_infos.start_normal_year_offset, start_normal); assert_eq!(check_infos.end_normal_year_offset, end_normal); @@ -742,12 +743,12 @@ mod tests { } #[test] - fn test_check_dst_transition_rules_consistency() -> Result<()> { + fn test_check_dst_transition_rules_consistency() -> Result<(), TzError> { let utc = LocalTimeType::utc(); - let julian_1 = |year_day| Result::Ok(RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(year_day)?)); - let julian_0 = |year_day| Result::Ok(RuleDay::Julian0WithLeap(Julian0WithLeap::new(year_day)?)); - let mwd = |month, week, week_day| Result::Ok(RuleDay::MonthWeekDay(MonthWeekDay::new(month, week, week_day)?)); + let julian_1 = |year_day| -> Result<_, TzError> { Ok(RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(year_day)?)) }; + let julian_0 = |year_day| -> Result<_, TzError> { Ok(RuleDay::Julian0WithLeap(Julian0WithLeap::new(year_day)?)) }; + let mwd = |month, week, week_day| -> Result<_, TzError> { Ok(RuleDay::MonthWeekDay(MonthWeekDay::new(month, week, week_day)?)) }; let check = |dst_start, dst_start_time, dst_end, dst_end_time| { let check_1 = check_dst_transition_rules_consistency(&utc, &utc, dst_start, dst_start_time, dst_end, dst_end_time); @@ -856,7 +857,7 @@ mod tests { } #[test] - fn test_rule_day() -> Result<()> { + fn test_rule_day() -> Result<(), TzError> { let rule_day_j1 = RuleDay::Julian1WithoutLeap(Julian1WithoutLeap::new(60)?); assert_eq!(rule_day_j1.transition_date(2000), (3, 1)); assert_eq!(rule_day_j1.transition_date(2001), (3, 1)); @@ -891,7 +892,7 @@ mod tests { } #[test] - fn test_transition_rule() -> Result<()> { + fn test_transition_rule() -> Result<(), TzError> { let transition_rule_fixed = TransitionRule::Fixed(LocalTimeType::new(-36000, false, None)?); assert_eq!(transition_rule_fixed.find_local_time_type(0)?.ut_offset(), -36000); @@ -967,7 +968,7 @@ mod tests { } #[test] - fn test_transition_rule_overflow() -> Result<()> { + fn test_transition_rule_overflow() -> Result<(), TzError> { let transition_rule_1 = TransitionRule::Alternate(AlternateTime::new( LocalTimeType::new(-1, false, None)?, LocalTimeType::new(-1, true, None)?, @@ -986,8 +987,8 @@ mod tests { 0, )?); - assert!(matches!(transition_rule_1.find_local_time_type(i64::MIN), Err(OutOfRangeError(_)))); - assert!(matches!(transition_rule_2.find_local_time_type(i64::MAX), Err(OutOfRangeError(_)))); + assert!(matches!(transition_rule_1.find_local_time_type(i64::MIN), Err(TzError::OutOfRange))); + assert!(matches!(transition_rule_2.find_local_time_type(i64::MAX), Err(TzError::OutOfRange))); Ok(()) } diff --git a/src/utils/const_fns.rs b/src/utils/const_fns.rs index febd938..7672018 100644 --- a/src/utils/const_fns.rs +++ b/src/utils/const_fns.rs @@ -1,6 +1,6 @@ //! Some useful constant functions. -use crate::error::OutOfRangeError; +use crate::error::TzError; use crate::timezone::{LeapSecond, Transition}; use core::cmp::Ordering; @@ -33,18 +33,18 @@ macro_rules! impl_try_into_integer { if min <= $value && $value <= max { Ok($value as $to_type) } else { - Err(OutOfRangeError("out of range integer conversion")) + Err(TzError::OutOfRange) } }}; } /// Convert a `i64` value to a `i32` value -pub(crate) const fn try_into_i32(value: i64) -> Result { +pub(crate) const fn try_into_i32(value: i64) -> Result { impl_try_into_integer!(i64, i32, value) } /// Convert a `i128` value to a `i64` value -pub(crate) const fn try_into_i64(value: i128) -> Result { +pub(crate) const fn try_into_i64(value: i128) -> Result { impl_try_into_integer!(i128, i64, value) }