shared: use structured errors for shared/itime

This commit is contained in:
Andrew Gallant 2025-12-19 15:55:39 -05:00
parent 3a832162be
commit 973876a9f1
13 changed files with 394 additions and 175 deletions

View file

@ -79,7 +79,7 @@ fn copy(srcdir: &Path, dstdir: &Path, dir: &Path) -> anyhow::Result<()> {
fn copy_rust_source_file(src: &Path, dst: &Path) -> anyhow::Result<()> {
let code = fs::read_to_string(src)
.with_context(|| format!("failed to read {}", src.display()))?;
let code = remove_only_jiffs(&remove_cfg_alloc(&code));
let code = remove_only_jiffs(&remove_cfg_alloc_or_std(&code));
let mut out = String::new();
writeln!(out, "// auto-generated by: jiff-cli generate shared")?;
@ -105,13 +105,13 @@ fn remove_only_jiffs(code: &str) -> String {
RE.replace_all(code, "").into_owned()
}
/// Removes all `#[cfg(feature = "alloc")]` gates.
/// Removes all `#[cfg(feature = "alloc|std")]` gates.
///
/// This is because the proc-macro always runs in a context where `alloc`
/// (and `std`) are enabled.
fn remove_cfg_alloc(code: &str) -> String {
fn remove_cfg_alloc_or_std(code: &str) -> String {
static RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r###"#\[cfg\(feature = "alloc"\)\]\n"###).unwrap()
Regex::new(r###"#\[cfg\(feature = "(alloc|std)"\)\]\n"###).unwrap()
});
RE.replace_all(code, "").into_owned()
}

View file

@ -0,0 +1,100 @@
// auto-generated by: jiff-cli generate shared
use crate::shared::{
error,
util::itime::{days_in_month, days_in_year, IEpochDay},
};
// N.B. Every variant in this error type is a range error.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum Error {
DateInvalidDayOfYear { year: i16 },
DateInvalidDayOfYearNoLeap,
DateInvalidDays { year: i16, month: i8 },
DateTimeSeconds,
// TODO: I believe this can never happen.
DayOfYear,
EpochDayDays,
EpochDayI32,
NthWeekdayOfMonth,
Tomorrow,
YearNext,
YearPrevious,
Yesterday,
}
impl From<Error> for error::Error {
#[cold]
#[inline(never)]
fn from(err: Error) -> error::Error {
error::ErrorKind::Time(err).into()
}
}
// impl error::IntoError for Error {
// fn into_error(self) -> error::Error {
// self.into()
// }
// }
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::Error::*;
match *self {
DateInvalidDayOfYear { year } => write!(
f,
"number of days for `{year:04}` is invalid, \
must be in range `1..={max_day}`",
max_day = days_in_year(year),
),
DateInvalidDayOfYearNoLeap => f.write_str(
"number of days is invalid, must be in range `1..=365`",
),
DateInvalidDays { year, month } => write!(
f,
"number of days for `{year:04}-{month:02}` is invalid, \
must be in range `1..={max_day}`",
max_day = days_in_month(year, month),
),
DateTimeSeconds => {
f.write_str("adding seconds to datetime overflowed")
}
DayOfYear => f.write_str("day of year is invalid"),
EpochDayDays => write!(
f,
"adding to epoch day resulted in a value outside \
the allowed range of `{min}..={max}`",
min = IEpochDay::MIN.epoch_day,
max = IEpochDay::MAX.epoch_day,
),
EpochDayI32 => f.write_str(
"adding to epoch day overflowed 32-bit signed integer",
),
NthWeekdayOfMonth => f.write_str(
"invalid nth weekday of month, \
must be non-zero and in range `-5..=5`",
),
Tomorrow => f.write_str(
"returning tomorrow for `9999-12-31` is not \
possible because it is greater than Jiff's supported
maximum date",
),
YearNext => f.write_str(
"creating a date for a year following `9999` is \
not possible because it is greater than Jiff's supported \
maximum date",
),
YearPrevious => f.write_str(
"creating a date for a year preceding `-9999` is \
not possible because it is less than Jiff's supported \
minimum date",
),
Yesterday => f.write_str(
"returning yesterday for `-9999-01-01` is not \
possible because it is less than Jiff's supported
minimum date",
),
}
}
}

View file

@ -0,0 +1,57 @@
// auto-generated by: jiff-cli generate shared
pub(crate) mod itime;
/// An error scoped to Jiff's `shared` module.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Error {
kind: ErrorKind,
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
self.kind.fmt(f)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum ErrorKind {
Time(self::itime::Error),
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error { kind }
}
}
impl core::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match *self {
ErrorKind::Time(ref err) => err.fmt(f),
}
}
}
/*
/// A slim error that occurs when an input value is out of bounds.
#[derive(Clone, Debug)]
struct SlimRangeError {
what: &'static str,
}
impl SlimRangeError {
fn new(what: &'static str) -> SlimRangeError {
SlimRangeError { what }
}
}
impl std::error::Error for SlimRangeError {}
impl core::fmt::Display for SlimRangeError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let SlimRangeError { what } = *self;
write!(f, "parameter '{what}' is not in the required range")
}
}
*/

View file

@ -474,6 +474,7 @@ pub struct PosixOffset {
// Does not require `alloc`, but is only used when `alloc` is enabled.
pub(crate) mod crc32;
pub(crate) mod error;
pub(crate) mod posix;
pub(crate) mod tzif;
pub(crate) mod util;

View file

@ -24,7 +24,7 @@ they are internal types. Specifically, to distinguish them from Jiff's public
types. For example, `Date` versus `IDate`.
*/
use super::error::{err, Error};
use crate::shared::error::{itime::Error as E, Error};
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub(crate) struct ITimestamp {
@ -144,10 +144,12 @@ impl IDateTime {
&self,
seconds: i32,
) -> Result<IDateTime, Error> {
let day_second =
self.time.to_second().second.checked_add(seconds).ok_or_else(
|| err!("adding `{seconds}s` to datetime overflowed"),
)?;
let day_second = self
.time
.to_second()
.second
.checked_add(seconds)
.ok_or_else(|| Error::from(E::DateTimeSeconds))?;
let days = day_second.div_euclid(86400);
let second = day_second.rem_euclid(86400);
let date = self.date.checked_add_days(days)?;
@ -162,8 +164,8 @@ pub(crate) struct IEpochDay {
}
impl IEpochDay {
const MIN: IEpochDay = IEpochDay { epoch_day: -4371587 };
const MAX: IEpochDay = IEpochDay { epoch_day: 2932896 };
pub(crate) const MIN: IEpochDay = IEpochDay { epoch_day: -4371587 };
pub(crate) const MAX: IEpochDay = IEpochDay { epoch_day: 2932896 };
/// Converts days since the Unix epoch to a Gregorian date.
///
@ -221,18 +223,12 @@ impl IEpochDay {
#[inline]
pub(crate) fn checked_add(&self, amount: i32) -> Result<IEpochDay, Error> {
let epoch_day = self.epoch_day;
let sum = epoch_day.checked_add(amount).ok_or_else(|| {
err!("adding `{amount}` to epoch day `{epoch_day}` overflowed i32")
})?;
let sum = epoch_day
.checked_add(amount)
.ok_or_else(|| Error::from(E::EpochDayI32))?;
let ret = IEpochDay { epoch_day: sum };
if !(IEpochDay::MIN <= ret && ret <= IEpochDay::MAX) {
return Err(err!(
"adding `{amount}` to epoch day `{epoch_day}` \
resulted in `{sum}`, which is not in the required \
epoch day range of `{min}..={max}`",
min = IEpochDay::MIN.epoch_day,
max = IEpochDay::MAX.epoch_day,
));
return Err(Error::from(E::EpochDayDays));
}
Ok(ret)
}
@ -264,10 +260,7 @@ impl IDate {
if day > 28 {
let max_day = days_in_month(year, month);
if day > max_day {
return Err(err!(
"day={day} is out of range for year={year} \
and month={month}, must be in range 1..={max_day}",
));
return Err(Error::from(E::DateInvalidDays { year, month }));
}
}
Ok(IDate { year, month, day })
@ -285,35 +278,19 @@ impl IDate {
day: i16,
) -> Result<IDate, Error> {
if !(1 <= day && day <= 366) {
return Err(err!(
"day-of-year={day} is out of range for year={year}, \
must be in range 1..={max_day}",
max_day = days_in_year(year),
));
return Err(Error::from(E::DateInvalidDayOfYear { year }));
}
let start = IDate { year, month: 1, day: 1 }.to_epoch_day();
let end = start
.checked_add(i32::from(day) - 1)
.map_err(|_| {
err!(
"failed to find date for \
year={year} and day-of-year={day}: \
adding `{day}` to `{start}` overflows \
Jiff's range",
start = start.epoch_day,
)
})?
.map_err(|_| Error::from(E::DayOfYear))?
.to_date();
// If we overflowed into the next year, then `day` is too big.
if year != end.year {
// Can only happen given day=366 and this is a leap year.
debug_assert_eq!(day, 366);
debug_assert!(!is_leap_year(year));
return Err(err!(
"day-of-year={day} is out of range for year={year}, \
must be in range 1..={max_day}",
max_day = days_in_year(year),
));
return Err(Error::from(E::DateInvalidDayOfYear { year }));
}
Ok(end)
}
@ -331,10 +308,7 @@ impl IDate {
mut day: i16,
) -> Result<IDate, Error> {
if !(1 <= day && day <= 365) {
return Err(err!(
"day-of-year={day} is out of range for year={year}, \
must be in range 1..=365",
));
return Err(Error::from(E::DateInvalidDayOfYearNoLeap));
}
if day >= 60 && is_leap_year(year) {
day += 1;
@ -394,10 +368,7 @@ impl IDate {
weekday: IWeekday,
) -> Result<IDate, Error> {
if nth == 0 || !(-5 <= nth && nth <= 5) {
return Err(err!(
"got nth weekday of `{nth}`, but \
must be non-zero and in range `-5..=5`",
));
return Err(Error::from(E::NthWeekdayOfMonth));
}
if nth > 0 {
let first_weekday = self.first_of_month().weekday();
@ -414,13 +385,10 @@ impl IDate {
// of `Day`, we can't let this boundary condition escape. So we
// check it here.
if day < 1 {
return Err(err!(
"day={day} is out of range for year={year} \
and month={month}, must be in range 1..={max_day}",
year = self.year,
month = self.month,
max_day = days_in_month(self.year, self.month),
));
return Err(Error::from(E::DateInvalidDays {
year: self.year,
month: self.month,
}));
}
IDate::try_new(self.year, self.month, day)
}
@ -433,11 +401,7 @@ impl IDate {
if self.month == 1 {
let year = self.year - 1;
if year <= -10000 {
return Err(err!(
"returning yesterday for -9999-01-01 is not \
possible because it is less than Jiff's supported
minimum date",
));
return Err(Error::from(E::Yesterday));
}
return Ok(IDate { year, month: 12, day: 31 });
}
@ -455,11 +419,7 @@ impl IDate {
if self.month == 12 {
let year = self.year + 1;
if year >= 10000 {
return Err(err!(
"returning tomorrow for 9999-12-31 is not \
possible because it is greater than Jiff's supported
maximum date",
));
return Err(Error::from(E::Tomorrow));
}
return Ok(IDate { year, month: 1, day: 1 });
}
@ -474,14 +434,7 @@ impl IDate {
pub(crate) fn prev_year(self) -> Result<i16, Error> {
let year = self.year - 1;
if year <= -10_000 {
return Err(err!(
"returning previous year for {year:04}-{month:02}-{day:02} is \
not possible because it is less than Jiff's supported \
minimum date",
year = self.year,
month = self.month,
day = self.day,
));
return Err(Error::from(E::YearPrevious));
}
Ok(year)
}
@ -491,14 +444,7 @@ impl IDate {
pub(crate) fn next_year(self) -> Result<i16, Error> {
let year = self.year + 1;
if year >= 10_000 {
return Err(err!(
"returning next year for {year:04}-{month:02}-{day:02} is \
not possible because it is greater than Jiff's supported \
maximum date",
year = self.year,
month = self.month,
day = self.day,
));
return Err(Error::from(E::YearNext));
}
Ok(year)
}

View file

@ -903,7 +903,9 @@ impl Date {
let weekday = weekday.to_iweekday();
let idate = self.to_idate_const();
Ok(Date::from_idate_const(
idate.nth_weekday_of_month(nth, weekday).map_err(Error::shared)?,
idate
.nth_weekday_of_month(nth, weekday)
.map_err(Error::shared2)?,
))
}
@ -3189,13 +3191,13 @@ impl DateWith {
Some(DateWithDay::OfYear(day)) => {
let year = year.get_unchecked();
let idate = IDate::from_day_of_year(year, day)
.map_err(Error::shared)?;
.map_err(Error::shared2)?;
return Ok(Date::from_idate_const(idate));
}
Some(DateWithDay::OfYearNoLeap(day)) => {
let year = year.get_unchecked();
let idate = IDate::from_day_of_year_no_leap(year, day)
.map_err(Error::shared)?;
.map_err(Error::shared2)?;
return Ok(Date::from_idate_const(idate));
}
};

View file

@ -1,4 +1,9 @@
use crate::{shared::util::error::Error as SharedError, util::sync::Arc};
use crate::{
shared::{
error::Error as SharedError2, util::error::Error as SharedError,
},
util::sync::Arc,
};
pub(crate) mod civil;
pub(crate) mod duration;
@ -151,6 +156,11 @@ impl Error {
Error::from(ErrorKind::Shared(err))
}
/// Creates a new error from the special "shared" error type.
pub(crate) fn shared2(err: SharedError2) -> Error {
Error::from(ErrorKind::Shared2(err))
}
/// A convenience constructor for building an I/O error.
///
/// This returns an error that is just a simple wrapper around the
@ -281,6 +291,7 @@ enum ErrorKind {
Range(RangeError),
RoundingIncrement(self::util::RoundingIncrementError),
Shared(SharedError),
Shared2(SharedError2),
SignedDuration(self::signed_duration::Error),
SlimRange(SlimRangeError),
Span(self::span::Error),
@ -324,6 +335,7 @@ impl core::fmt::Display for ErrorKind {
Range(ref err) => err.fmt(f),
RoundingIncrement(ref err) => err.fmt(f),
Shared(ref err) => err.fmt(f),
Shared2(ref err) => err.fmt(f),
SignedDuration(ref err) => err.fmt(f),
SlimRange(ref err) => err.fmt(f),
Span(ref err) => err.fmt(f),

View file

@ -2079,8 +2079,8 @@ impl BrokenDownTime {
/// // An error only occurs when you try to extract a date:
/// assert_eq!(
/// tm.to_date().unwrap_err().to_string(),
/// "invalid date: day-of-year=366 is out of range \
/// for year=2023, must be in range 1..=365",
/// "invalid date: number of days for `2023` is invalid, \
/// must be in range `1..=365`",
/// );
/// // But parsing a value that is always illegal will
/// // result in an error:

98
src/shared/error/itime.rs Normal file
View file

@ -0,0 +1,98 @@
use crate::shared::{
error,
util::itime::{days_in_month, days_in_year, IEpochDay},
};
// N.B. Every variant in this error type is a range error.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum Error {
DateInvalidDayOfYear { year: i16 },
DateInvalidDayOfYearNoLeap,
DateInvalidDays { year: i16, month: i8 },
DateTimeSeconds,
// TODO: I believe this can never happen.
DayOfYear,
EpochDayDays,
EpochDayI32,
NthWeekdayOfMonth,
Tomorrow,
YearNext,
YearPrevious,
Yesterday,
}
impl From<Error> for error::Error {
#[cold]
#[inline(never)]
fn from(err: Error) -> error::Error {
error::ErrorKind::Time(err).into()
}
}
// impl error::IntoError for Error {
// fn into_error(self) -> error::Error {
// self.into()
// }
// }
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::Error::*;
match *self {
DateInvalidDayOfYear { year } => write!(
f,
"number of days for `{year:04}` is invalid, \
must be in range `1..={max_day}`",
max_day = days_in_year(year),
),
DateInvalidDayOfYearNoLeap => f.write_str(
"number of days is invalid, must be in range `1..=365`",
),
DateInvalidDays { year, month } => write!(
f,
"number of days for `{year:04}-{month:02}` is invalid, \
must be in range `1..={max_day}`",
max_day = days_in_month(year, month),
),
DateTimeSeconds => {
f.write_str("adding seconds to datetime overflowed")
}
DayOfYear => f.write_str("day of year is invalid"),
EpochDayDays => write!(
f,
"adding to epoch day resulted in a value outside \
the allowed range of `{min}..={max}`",
min = IEpochDay::MIN.epoch_day,
max = IEpochDay::MAX.epoch_day,
),
EpochDayI32 => f.write_str(
"adding to epoch day overflowed 32-bit signed integer",
),
NthWeekdayOfMonth => f.write_str(
"invalid nth weekday of month, \
must be non-zero and in range `-5..=5`",
),
Tomorrow => f.write_str(
"returning tomorrow for `9999-12-31` is not \
possible because it is greater than Jiff's supported
maximum date",
),
YearNext => f.write_str(
"creating a date for a year following `9999` is \
not possible because it is greater than Jiff's supported \
maximum date",
),
YearPrevious => f.write_str(
"creating a date for a year preceding `-9999` is \
not possible because it is less than Jiff's supported \
minimum date",
),
Yesterday => f.write_str(
"returning yesterday for `-9999-01-01` is not \
possible because it is less than Jiff's supported
minimum date",
),
}
}
}

56
src/shared/error/mod.rs Normal file
View file

@ -0,0 +1,56 @@
pub(crate) mod itime;
/// An error scoped to Jiff's `shared` module.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Error {
kind: ErrorKind,
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
self.kind.fmt(f)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum ErrorKind {
Time(self::itime::Error),
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error { kind }
}
}
impl core::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match *self {
ErrorKind::Time(ref err) => err.fmt(f),
}
}
}
/*
/// A slim error that occurs when an input value is out of bounds.
#[derive(Clone, Debug)]
struct SlimRangeError {
what: &'static str,
}
impl SlimRangeError {
fn new(what: &'static str) -> SlimRangeError {
SlimRangeError { what }
}
}
#[cfg(feature = "std")]
impl std::error::Error for SlimRangeError {}
impl core::fmt::Display for SlimRangeError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let SlimRangeError { what } = *self;
write!(f, "parameter '{what}' is not in the required range")
}
}
*/

View file

@ -502,6 +502,7 @@ impl PosixTimeZone<&'static str> {
// Does not require `alloc`, but is only used when `alloc` is enabled.
#[cfg(feature = "alloc")]
pub(crate) mod crc32;
pub(crate) mod error;
pub(crate) mod posix;
#[cfg(feature = "alloc")]
pub(crate) mod tzif;

View file

@ -230,8 +230,8 @@ impl TzifOwned {
let clamped = timestamp.clamp(TIMESTAMP_MIN, TIMESTAMP_MAX);
// only-jiff-start
warn!(
"found Unix timestamp {timestamp} that is outside \
Jiff's supported range, clamping to {clamped}",
"found Unix timestamp `{timestamp}` that is outside \
Jiff's supported range, clamping to `{clamped}`",
);
// only-jiff-end
timestamp = clamped;
@ -378,7 +378,7 @@ impl TzifOwned {
if !(TIMESTAMP_MIN <= occur && occur <= TIMESTAMP_MAX) {
// only-jiff-start
warn!(
"leap second occurrence {occur} is \
"leap second occurrence `{occur}` is \
not in Jiff's supported range"
)
// only-jiff-end

View file

@ -22,7 +22,7 @@ they are internal types. Specifically, to distinguish them from Jiff's public
types. For example, `Date` versus `IDate`.
*/
use super::error::{err, Error};
use crate::shared::error::{itime::Error as E, Error};
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub(crate) struct ITimestamp {
@ -142,10 +142,12 @@ impl IDateTime {
&self,
seconds: i32,
) -> Result<IDateTime, Error> {
let day_second =
self.time.to_second().second.checked_add(seconds).ok_or_else(
|| err!("adding `{seconds}s` to datetime overflowed"),
)?;
let day_second = self
.time
.to_second()
.second
.checked_add(seconds)
.ok_or_else(|| Error::from(E::DateTimeSeconds))?;
let days = day_second.div_euclid(86400);
let second = day_second.rem_euclid(86400);
let date = self.date.checked_add_days(days)?;
@ -160,8 +162,8 @@ pub(crate) struct IEpochDay {
}
impl IEpochDay {
const MIN: IEpochDay = IEpochDay { epoch_day: -4371587 };
const MAX: IEpochDay = IEpochDay { epoch_day: 2932896 };
pub(crate) const MIN: IEpochDay = IEpochDay { epoch_day: -4371587 };
pub(crate) const MAX: IEpochDay = IEpochDay { epoch_day: 2932896 };
/// Converts days since the Unix epoch to a Gregorian date.
///
@ -219,18 +221,12 @@ impl IEpochDay {
#[inline]
pub(crate) fn checked_add(&self, amount: i32) -> Result<IEpochDay, Error> {
let epoch_day = self.epoch_day;
let sum = epoch_day.checked_add(amount).ok_or_else(|| {
err!("adding `{amount}` to epoch day `{epoch_day}` overflowed i32")
})?;
let sum = epoch_day
.checked_add(amount)
.ok_or_else(|| Error::from(E::EpochDayI32))?;
let ret = IEpochDay { epoch_day: sum };
if !(IEpochDay::MIN <= ret && ret <= IEpochDay::MAX) {
return Err(err!(
"adding `{amount}` to epoch day `{epoch_day}` \
resulted in `{sum}`, which is not in the required \
epoch day range of `{min}..={max}`",
min = IEpochDay::MIN.epoch_day,
max = IEpochDay::MAX.epoch_day,
));
return Err(Error::from(E::EpochDayDays));
}
Ok(ret)
}
@ -262,10 +258,7 @@ impl IDate {
if day > 28 {
let max_day = days_in_month(year, month);
if day > max_day {
return Err(err!(
"day={day} is out of range for year={year} \
and month={month}, must be in range 1..={max_day}",
));
return Err(Error::from(E::DateInvalidDays { year, month }));
}
}
Ok(IDate { year, month, day })
@ -283,35 +276,19 @@ impl IDate {
day: i16,
) -> Result<IDate, Error> {
if !(1 <= day && day <= 366) {
return Err(err!(
"day-of-year={day} is out of range for year={year}, \
must be in range 1..={max_day}",
max_day = days_in_year(year),
));
return Err(Error::from(E::DateInvalidDayOfYear { year }));
}
let start = IDate { year, month: 1, day: 1 }.to_epoch_day();
let end = start
.checked_add(i32::from(day) - 1)
.map_err(|_| {
err!(
"failed to find date for \
year={year} and day-of-year={day}: \
adding `{day}` to `{start}` overflows \
Jiff's range",
start = start.epoch_day,
)
})?
.map_err(|_| Error::from(E::DayOfYear))?
.to_date();
// If we overflowed into the next year, then `day` is too big.
if year != end.year {
// Can only happen given day=366 and this is a leap year.
debug_assert_eq!(day, 366);
debug_assert!(!is_leap_year(year));
return Err(err!(
"day-of-year={day} is out of range for year={year}, \
must be in range 1..={max_day}",
max_day = days_in_year(year),
));
return Err(Error::from(E::DateInvalidDayOfYear { year }));
}
Ok(end)
}
@ -329,10 +306,7 @@ impl IDate {
mut day: i16,
) -> Result<IDate, Error> {
if !(1 <= day && day <= 365) {
return Err(err!(
"day-of-year={day} is out of range for year={year}, \
must be in range 1..=365",
));
return Err(Error::from(E::DateInvalidDayOfYearNoLeap));
}
if day >= 60 && is_leap_year(year) {
day += 1;
@ -392,10 +366,7 @@ impl IDate {
weekday: IWeekday,
) -> Result<IDate, Error> {
if nth == 0 || !(-5 <= nth && nth <= 5) {
return Err(err!(
"got nth weekday of `{nth}`, but \
must be non-zero and in range `-5..=5`",
));
return Err(Error::from(E::NthWeekdayOfMonth));
}
if nth > 0 {
let first_weekday = self.first_of_month().weekday();
@ -412,13 +383,10 @@ impl IDate {
// of `Day`, we can't let this boundary condition escape. So we
// check it here.
if day < 1 {
return Err(err!(
"day={day} is out of range for year={year} \
and month={month}, must be in range 1..={max_day}",
year = self.year,
month = self.month,
max_day = days_in_month(self.year, self.month),
));
return Err(Error::from(E::DateInvalidDays {
year: self.year,
month: self.month,
}));
}
IDate::try_new(self.year, self.month, day)
}
@ -431,11 +399,7 @@ impl IDate {
if self.month == 1 {
let year = self.year - 1;
if year <= -10000 {
return Err(err!(
"returning yesterday for -9999-01-01 is not \
possible because it is less than Jiff's supported
minimum date",
));
return Err(Error::from(E::Yesterday));
}
return Ok(IDate { year, month: 12, day: 31 });
}
@ -453,11 +417,7 @@ impl IDate {
if self.month == 12 {
let year = self.year + 1;
if year >= 10000 {
return Err(err!(
"returning tomorrow for 9999-12-31 is not \
possible because it is greater than Jiff's supported
maximum date",
));
return Err(Error::from(E::Tomorrow));
}
return Ok(IDate { year, month: 1, day: 1 });
}
@ -472,14 +432,7 @@ impl IDate {
pub(crate) fn prev_year(self) -> Result<i16, Error> {
let year = self.year - 1;
if year <= -10_000 {
return Err(err!(
"returning previous year for {year:04}-{month:02}-{day:02} is \
not possible because it is less than Jiff's supported \
minimum date",
year = self.year,
month = self.month,
day = self.day,
));
return Err(Error::from(E::YearPrevious));
}
Ok(year)
}
@ -489,14 +442,7 @@ impl IDate {
pub(crate) fn next_year(self) -> Result<i16, Error> {
let year = self.year + 1;
if year >= 10_000 {
return Err(err!(
"returning next year for {year:04}-{month:02}-{day:02} is \
not possible because it is greater than Jiff's supported \
maximum date",
year = self.year,
month = self.month,
day = self.day,
));
return Err(Error::from(E::YearNext));
}
Ok(year)
}