Skip to main content

jiff_core/
bounds.rs

1/*!
2Defines boundary types and their corresponding error values.
3
4The main trait in this module is [`Bounds`]. It is implemented by each of
5several boundary types describing a range of contiguous values used by Jiff.
6For example, [`Year`] corresponds to the range of support calendar year values.
7
8Each range type has concrete methods defined on it that simply forward to its
9corresponding method on `Bounds`. For example, [`Year::check`] forwards to the
10default implementation of [`Bounds::check`]. These concrete wrappers exist to
11make calling the routines more ergonomic. For example, they don't require that
12the `Bounds` trait be in scope. Additionally, a [`Year::checkc`] concrete
13method is provide for use in `const` contexts. This is not available on the
14`Bounds` trait because generics are not yet supported (as of 2026-02-13) in
15a `const` context.
16*/
17
18use crate::constants as c;
19
20/// This macro writes out the boiler plate to define a boundary type.
21///
22/// Specifically, it implements the `Bounds` trait and provides a few
23/// concrete methods. The concrete methods are mostly wrappers around
24/// the generic trait methods. They are provided so that callers don't
25/// have to import the `Bounds` trait to use them.
26macro_rules! define_bounds {
27    ($(
28        $(#[$attr:meta])*
29        (
30            // The name of the boundary type.
31            $name:ident,
32            // The underlying primitive type. This is usually, but not always,
33            // the smallest signed primitive integer type that can represent
34            // both the minimum and maximum boundary values.
35            $ty:ident,
36            // A short human readable description that appears in error
37            // messages when the boundaries of this type are violated.
38            $what:expr,
39            // The minimum value.
40            $min:expr,
41            // The maximum value.
42            $max:expr $(,)?
43        )
44    ),* $(,)?) => {
45        $(
46            $(#[$attr])*
47            #[allow(missing_debug_implementations)]
48            #[allow(missing_docs)]
49            #[derive(Eq, PartialEq)]
50            pub struct $name(());
51
52            impl Bounds for $name {
53                const WHAT: &'static str = $what;
54                const MIN: Self::Primitive = $min;
55                const MAX: Self::Primitive = $max;
56                type Primitive = $ty;
57                type Error = BoundsError;
58
59                #[cold]
60                #[inline(never)]
61                fn error() -> BoundsError {
62                    Self::error()
63                }
64            }
65
66            #[allow(dead_code)]
67            #[allow(missing_docs)]
68            impl $name {
69                pub const MIN: $ty = <$name as Bounds>::MIN;
70                pub const MAX: $ty = <$name as Bounds>::MAX;
71                pub const LEN: i128 = Self::MAX as i128 - Self::MIN as i128 + 1;
72
73                #[cold]
74                pub const fn error() -> BoundsError {
75                    BoundsError {
76                        kind: BoundsErrorKind::$name(RawBoundsError::new()),
77                    }
78                }
79
80                #[inline(always)]
81                pub fn check(n: impl Into<i64>) -> Result<$ty, BoundsError> {
82                    <$name as Bounds>::check(n)
83                }
84
85                #[inline(always)]
86                pub const fn checkc(n: i64) -> Result<$ty, BoundsError> {
87                    match self::const_check::$ty(n) {
88                        Ok(n) => Ok(n),
89                        Err(err) => Err(BoundsError {
90                            kind: BoundsErrorKind::$name(err),
91                        }),
92                    }
93                }
94
95                #[inline(always)]
96                pub const fn checked_add(n1: $ty, n2: $ty) -> Result<$ty, BoundsError> {
97                    match self::const_checked_add::$ty(n1, n2) {
98                        Ok(n) => Ok(n),
99                        Err(err) => Err(BoundsError {
100                            kind: BoundsErrorKind::$name(err),
101                        }),
102                    }
103                }
104
105                #[inline(always)]
106                pub fn checked_mul(n1: $ty, n2: $ty) -> Result<$ty, BoundsError> {
107                    <$name as Bounds>::checked_mul(n1, n2)
108                }
109
110                #[cfg(test)]
111                pub(crate) fn arbitrary(g: &mut quickcheck::Gen) -> $ty {
112                    use quickcheck::Arbitrary;
113
114                    let mut n: $ty = <$ty>::arbitrary(g);
115                    n = n.wrapping_rem_euclid(Self::LEN as $ty);
116                    n += Self::MIN;
117                    n
118                }
119            }
120        )*
121
122        /// An error that indicates a value is out of its intended range.
123        #[derive(Clone, Copy, Debug, Eq, PartialEq)]
124        #[cfg_attr(feature = "defmt", derive(defmt::Format))]
125        enum BoundsErrorKind {
126            $($name(RawBoundsError<$name>),)*
127        }
128
129        impl core::fmt::Display for BoundsErrorKind {
130            fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
131                match *self {
132                    $(BoundsErrorKind::$name(ref err) => err.fmt(f),)*
133                }
134            }
135        }
136    }
137}
138
139define_bounds! {
140    /// The supported range of nanoseconds in a single civil day.
141    (
142        CivilDayNanosecond,
143        i64,
144        "nanoseconds (in one civil day)",
145        0,
146        c::NANOS_PER_CIVIL_DAY - 1,
147    ),
148    /// The supported range of seconds in a single civil day.
149    (
150        CivilDaySecond,
151        i32,
152        "seconds (in one civil day)",
153        0,
154        c::SECS_PER_CIVIL_DAY_32 - 1,
155    ),
156    /// The supported range of "day of month."
157    ///
158    /// While the maximum value is 31, the actual maximum depends on the
159    /// specific month. The smallest possible maximum for any month is `28`
160    /// for February.
161    (Day, i8, "day", 1, 31),
162    /// The supported range of "day of year."
163    ///
164    /// This includes Feb 29 in leap years. For non-leap years, `366` is
165    /// invalid. The value `1` always corresponds to Jan 1.
166    (DayOfYear, i16, "day-of-year", 1, 366),
167    /// The supported range of "day of year," but never including Feb 29.
168    ///
169    /// This means that the valid range for all years is `1..=365`. And there
170    /// is no way to refer to Feb 29 in this scheme.
171    (DayOfYearNoLeap, i16, "day-of-year (skipping Feb 29)", 1, 365),
172    /// The range of seconds for the possible differences between any two pairs
173    /// of `(timestamp, offset)`.
174    ///
175    /// All of our span types (except for years and months, since they have
176    /// variable length even in civil datetimes) are defined in terms of this
177    /// constant. The way it's defined is a little odd, so let's break it down.
178    ///
179    /// Firstly, a span of seconds should be able to represent at least the
180    /// complete span supported by `Timestamp`. Thus, it's based off of
181    /// `UnixSeconds::LEN`. That is, a span should be able to represent the
182    /// value `UnixSeconds::MAX - UnixSeconds::MIN`.
183    ///
184    /// Secondly, a span should also be able to account for any amount of
185    /// possible time that a time zone offset might add or subtract to an
186    /// `Timestamp`. This also means it can account for any difference between
187    /// two `civil::DateTime` values.
188    ///
189    /// Thirdly, we would like our span to be divisible by
190    /// `SECONDS_PER_CIVIL_DAY`. This isn't strictly required, but it makes
191    /// defining boundaries a little smoother. If it weren't divisible, then the
192    /// lower bounds on some types would need to be adjusted by one.
193    ///
194    /// Note that neither the existence of this constant nor defining our
195    /// spans based on it impacts the correctness of doing arithmetic on zoned
196    /// instants. Arithmetic on zoned instants still uses "civil" spans, but the
197    /// length of time for some units (like a day) might vary. The arithmetic
198    /// for zoned instants accounts for this explicitly. But it still must obey
199    /// the limits set here.
200    (
201        DeltaSeconds,
202        i64,
203        "seconds",
204        -Self::MAX,
205        next_multiple_of(
206            UnixEpochSeconds::LEN as i64
207                + OffsetTotalSeconds::MAX as i64
208                + c::SECS_PER_CIVIL_DAY,
209            c::SECS_PER_CIVIL_DAY,
210        ),
211    ),
212    /// The range of hours supported.
213    ///
214    /// Jiff always uses the "24 hour clock" where midnight is hour `0` and
215    /// 11pm is `23`. The alternative spelling of midnight, `24:00`, is not
216    /// supported.
217    (Hour, i8, "hour", 0, 23),
218    /// The supported range of week numbers in the ISO 8601 week calendar.
219    ///
220    /// ISO 8601 leap years have exactly 53 weeks. Non-leap years have 52
221    /// weeks.
222    (ISOWeek, i8, "iso-week", 1, 53),
223    /// The supported range of year numbers in the ISO 8601 week calendar.
224    ///
225    /// This matches the supported range of Gregorian year numbers.
226    (ISOYear, i16, "iso-year", Year::MIN, Year::MAX),
227    /// The supported range of microsecond values.
228    (Microsecond, i16, "microsecond", 0, 999),
229    /// The supported range of millisecond values.
230    (Millisecond, i16, "millisecond", 0, 999),
231    /// The supported range of minute values.
232    (Minute, i8, "minute", 0, 59),
233    /// The supported range of month values.
234    (Month, i8, "month", 1, 12),
235    /// The supported range of nanosecond values.
236    (Nanosecond, i16, "nanosecond", 0, 999),
237    /// The supported range of values for getting the "nth weekday" from a
238    /// date.
239    ///
240    /// Note that `0` is also invalid, but this is not expressed by
241    /// this single contiguous range.
242    (
243        NthWeekday,
244        i32,
245        "nth weekday",
246        -Self::MAX,
247        (DeltaSeconds::MAX / c::SECS_PER_CIVIL_WEEK) as i32,
248    ),
249    /// The supported range of values for getting the "nth weekday of a month"
250    /// from a particular date.
251    ///
252    /// Note that `0` is also invalid, but this is not expressed by
253    /// this single contiguous range. Note also that `5` or `-5` can be invalid
254    /// if the provided month does not contain a 5th instance of the weekday
255    /// provided by the caller.
256    (NthWeekdayOfMonth, i8, "nth weekday of month", -5, 5),
257    /// The number of hours allowed in a time zone offset.
258    ///
259    /// This number was somewhat arbitrarily chosen. In part because it's
260    /// bigger than any current offset in actual use by a wide margin, and in
261    /// part because POSIX `TZ` strings require the ability to store offsets in
262    /// the range `-24:59:59..=25:59:59`. Note though that we make the range a
263    /// little bigger with `-25:59:59..=25:59:59` so that negating an offset
264    /// always produces a valid offset.
265    ///
266    /// Note that RFC 8536 actually allows offsets to be much bigger, namely,
267    /// in the range `(-2^31, 2^31)`, where both ends are _exclusive_ (`-2^31`
268    /// is explicitly disallowed, and `2^31` overflows a signed 32-bit
269    /// integer). But RFC 8536 does say that it *should* be in the range
270    /// `[-89999, 93599]`, which matches POSIX. In order to keep our offset
271    /// small, we stick roughly to what POSIX requires.
272    ///
273    /// Note that we support a slightly bigger range of offsets than Temporal.
274    /// Temporal seems to support only up to 23 hours, but we go up to 25
275    /// hours. This is done to support POSIX time zone strings, which also
276    /// require 25 hours (plus the maximal minute/second components).
277    (OffsetHours, i8, "time zone offset hours", -25, 25),
278    /// The supported range of "minute" component of a time zone offset.
279    (OffsetMinutes, i8, "time zone offset minutes", -59, 59),
280    /// The supported range of "second" component of a time zone offset.
281    (OffsetSeconds, i8, "time zone offset seconds", -59, 59),
282    /// The supported range of time zone offsets, expressed in units of
283    /// seconds.
284    (
285        OffsetTotalSeconds,
286        i32,
287        "time zone offset total seconds",
288        -Self::MAX,
289        (OffsetHours::MAX as i32 * c::SECS_PER_HOUR_32)
290        + (OffsetMinutes::MAX as i32 * c::MINS_PER_HOUR_32)
291        + OffsetSeconds::MAX as i32,
292    ),
293    /// The supported range of second values.
294    ///
295    /// Note that Jiff does not support leap seconds. So a value of `60`
296    /// is never valid. (Except when parsing. In which case, Jiff will
297    /// automatically clamp the value `60` to `59`.)
298    (Second, i8, "second", 0, 59),
299    /// The supported range of fractional seconds, expressed in units of
300    /// nanoseconds.
301    (SubsecNanosecond, i32, "subsecond nanosecond", 0, c::NANOS_PER_SEC_32 - 1),
302    /// The supported range of a timestamp's subsecond component.
303    ///
304    /// This is different from `SubsecNanosecond`, which applies to civil time
305    /// within a single day, in that it can be negative. For example, the
306    /// timestamp `-0s 123ms` refers to 123 milliseconds before the Unix epoch.
307    (
308        SignedSubsecNanosecond,
309        i32,
310        "subsecond nanosecond",
311        -SubsecNanosecond::MAX,
312        SubsecNanosecond::MAX,
313    ),
314    /// An error that occurs when doing arithmetic on a timestamp overflows
315    /// the `i32` representation of a nanosecond.
316    (TimestampArithmeticNanosecond, i32, "nanosecond", i32::MIN, i32::MAX),
317    /// The supported range of days from the Unix epoch for the Gregorian
318    /// calendar.
319    ///
320    /// The range supported is based on the range of Unix timestamps that we
321    /// support.
322    (
323        UnixEpochDays,
324        i32,
325        "Unix epoch days",
326        (UnixEpochSeconds::MIN + OffsetTotalSeconds::MIN as i64).div_euclid(c::SECS_PER_CIVIL_DAY) as i32,
327        (UnixEpochSeconds::MAX + OffsetTotalSeconds::MAX as i64).div_euclid(c::SECS_PER_CIVIL_DAY) as i32,
328    ),
329    (
330        UnixEpochMilliseconds,
331        i64,
332        "Unix timestamp milliseconds",
333        UnixEpochSeconds::MIN * c::MILLIS_PER_SEC,
334        UnixEpochSeconds::MAX * c::MILLIS_PER_SEC,
335    ),
336    (
337        UnixEpochMicroseconds,
338        i64,
339        "Unix timestamp microseconds",
340        UnixEpochMilliseconds::MIN * c::MICROS_PER_MILLI,
341        UnixEpochMilliseconds::MAX * c::MICROS_PER_MILLI,
342    ),
343    /// The supported range of seconds for the difference between any two
344    /// values of `UnixEpochDays`.
345    ///
346    /// This range should correspond to the first second of `Year::MIN`
347    /// (with a minimal offset) up through (and including) the last second
348    /// of `Year::MAX` (with a maximal offset). Actually computing that is
349    /// non-trivial, however, it can be computed easily enough using Unix
350    /// programs like `date`:
351    ///
352    /// ```text
353    /// $ TZ=0 date -d 'Mon Jan  1 12:00:00 AM  -9999' +'%s'
354    /// date: invalid date ‘Mon Jan  1 12:00:00 AM  -9999’
355    /// $ TZ=0 date -d 'Fri Dec 31 23:59:59  9999' +'%s'
356    /// 253402300799
357    /// ```
358    ///
359    /// Well, almost easily enough. `date` apparently doesn't support negative
360    /// years. But it does support negative timestamps:
361    ///
362    /// ```text
363    /// $ TZ=0 date -d '@-377705116800'
364    /// Mon Jan  1 12:00:00 AM  -9999
365    /// $ TZ=0 date -d '@253402300799'
366    /// Fri Dec 31 11:59:59 PM  9999
367    /// ```
368    ///
369    /// With that said, we actually end up restricting the range a bit more
370    /// than what's above. Namely, what's above is what we support for civil
371    /// datetimes. Because of time zones, we need to choose whether all
372    /// `Timestamp` values can be infallibly converted to `civil::DateTime`
373    /// values, or whether all `civil::DateTime` values can be infallibly
374    /// converted to `Timestamp` values. Jiff choses the former because getting
375    /// a civil datetime is important for formatting. If Jiff didn't choose the
376    /// former, there would be some timestamps that could not be formatted.
377    /// Thus, we make room by shrinking the range of allowed instants by
378    /// precisely the maximum supported time zone offset.
379    (
380        UnixEpochSeconds,
381        i64,
382        "Unix timestamp seconds",
383        -377705116800 - OffsetTotalSeconds::MIN as i64,
384        253402300799 - OffsetTotalSeconds::MAX as i64,
385    ),
386    /// The supported range of 0-offset week day values when weeks start on
387    /// Monday.
388    (WeekdayMondayZero, i8, "weekday (Monday 0-indexed)", 0, 6),
389    /// The supported range of 1-offset week day values when weeks start on
390    /// Monday.
391    (WeekdayMondayOne, i8, "weekday (Monday 1-indexed)", 1, 7),
392    /// The supported range of 0-offset week day values when weeks start on
393    /// Sunday.
394    (WeekdaySundayZero, i8, "weekday (Sunday 0-indexed)", 0, 6),
395    /// The supported range of 1-offset week day values when weeks start on
396    /// Sunday.
397    (WeekdaySundayOne, i8, "weekday (Sunday 1-indexed)", 1, 7),
398    /// The range of years supported.
399    (Year, i16, "year", -9999, 9999),
400    /// The range of years supported for the Common Era (CE).
401    (YearCE, i16, "CE year", 1, Year::MAX),
402    /// The range of years supported for Before Common Era (BCE).
403    (YearBCE, i16, "BCE year", 1, Year::MAX + 1),
404}
405
406/// A trait for making `x as int_type` usable in a generic context.
407///
408/// All of these methods require callers to ensure the cast is correct.
409/// However, when `debug_assertions` is enabled, the casts will result in
410/// a panic if they are incorrect.
411///
412/// Because of the extra checks when `debug_assertions` is enabled, Jiff tries
413/// to use these routines wherever possible in lieu of `as`. The primary
414/// downside of using this trait is that it doesn't work in a `const` context
415/// because Rust does not yet support generics in `const`.
416#[allow(missing_docs)]
417pub trait Primitive:
418    Clone
419    + Copy
420    + Eq
421    + PartialEq
422    + PartialOrd
423    + Ord
424    + core::fmt::Debug
425    + core::fmt::Display
426{
427    fn as_i8(self) -> i8;
428    fn as_i16(self) -> i16;
429    fn as_i32(self) -> i32;
430    fn as_i64(self) -> i64;
431
432    fn from_i8(n: i8) -> Self;
433    fn from_i16(n: i16) -> Self;
434    fn from_i32(n: i32) -> Self;
435    fn from_i64(n: i64) -> Self;
436
437    fn checked_add(self, n: Self) -> Option<Self>;
438    fn checked_mul(self, n: Self) -> Option<Self>;
439}
440
441macro_rules! impl_primitive {
442    ($($intty:ty),*) => {
443        $(
444            impl Primitive for $intty {
445                fn as_i8(self) -> i8 {
446                    #[cfg(debug_assertions)]
447                    {
448                        i8::try_from(self).unwrap()
449                    }
450                    #[cfg(not(debug_assertions))]
451                    {
452                        self as i8
453                    }
454                }
455
456                fn as_i16(self) -> i16 {
457                    #[cfg(debug_assertions)]
458                    {
459                        i16::try_from(self).unwrap()
460                    }
461                    #[cfg(not(debug_assertions))]
462                    {
463                        self as i16
464                    }
465                }
466
467                fn as_i32(self) -> i32 {
468                    #[cfg(debug_assertions)]
469                    {
470                        i32::try_from(self).unwrap()
471                    }
472                    #[cfg(not(debug_assertions))]
473                    {
474                        self as i32
475                    }
476                }
477
478                fn as_i64(self) -> i64 {
479                    #[cfg(debug_assertions)]
480                    {
481                        i64::try_from(self).unwrap()
482                    }
483                    #[cfg(not(debug_assertions))]
484                    {
485                        self as i64
486                    }
487                }
488
489                fn from_i8(n: i8) -> Self {
490                    #[cfg(debug_assertions)]
491                    {
492                        Self::try_from(n).unwrap()
493                    }
494                    #[cfg(not(debug_assertions))]
495                    {
496                        n as Self
497                    }
498                }
499
500                fn from_i16(n: i16) -> Self {
501                    #[cfg(debug_assertions)]
502                    {
503                        Self::try_from(n).unwrap()
504                    }
505                    #[cfg(not(debug_assertions))]
506                    {
507                        n as Self
508                    }
509                }
510
511                fn from_i32(n: i32) -> Self {
512                    #[cfg(debug_assertions)]
513                    {
514                        Self::try_from(n).unwrap()
515                    }
516                    #[cfg(not(debug_assertions))]
517                    {
518                        n as Self
519                    }
520                }
521
522                fn from_i64(n: i64) -> Self {
523                    #[cfg(debug_assertions)]
524                    {
525                        Self::try_from(n).unwrap()
526                    }
527                    #[cfg(not(debug_assertions))]
528                    {
529                        n as Self
530                    }
531                }
532
533                fn checked_add(self, n: $intty) -> Option<$intty> {
534                    <$intty>::checked_add(self, n)
535                }
536
537                fn checked_mul(self, n: $intty) -> Option<$intty> {
538                    <$intty>::checked_mul(self, n)
539                }
540            }
541        )*
542    }
543}
544
545impl_primitive!(i8, i16, i32, i64);
546
547/// An interface for defining boundaries on integer values.
548///
549/// An implementation of this trait defines a single contiguous range used
550/// inside of Jiff. For example, the allowed range of years is `-9999..=9999`.
551///
552/// Each implementation defines its primitive representation, which is usally
553/// the smallest signed integer type that can hold the minimum and maximum
554/// values. This trait provides `check`, `checked_add` and `checked_mul`
555/// methods that callers likely do not need to override.
556///
557/// Other than the associated types and constants, the only required method
558/// that callers must implement is `error()`.
559///
560/// # History & Design
561///
562/// It took a lot of design iteration to arrive at this trait.
563/// [Jiff originally started with an Ada-inspired ranged integer
564/// abstraction][jiff-range-history]. At first, it worked really well and
565/// seemed to do a decent job at uncovering bugs related to values going
566/// outside of their intended range. Or worse, actual integer overflow. This
567/// particular abstraction had three key properties:
568///
569/// 1. Each ranged integer type corresponded to a single contiguous range
570/// (e.g., `Year` was `-9999..=9999`), and the range values were defined by
571/// const type parameters.
572/// 2. The value inside of a ranged integer was permitted to "drift" outside
573/// of its defined range (but not outside of its primitive representation).
574/// A panic would only manifest when one tried to look inside the ranged
575/// integer and access its primitive representation (e.g., `Date::year`).
576/// 3. Ranged integers kept track of their minimum and maximum possible values,
577/// *at runtime*, but only when `debug_assertions` were enabled. So when you
578/// did `x + y`, the result wouldn't just be the actual addition, but also
579/// addition performed on the corresponding min and max values on each of `x`
580/// and `y`.
581///
582/// When all three of these properties combined, you got an excellent bug
583/// finding tool without necessarily needing to write tests covering all of the
584/// edge cases. For example, if you did `x + y` and the result only went out
585/// of range when `x` and `y` were some extreme values, you'd get an immediate
586/// panic once you accessed the value even when using non-extreme values.
587/// That's because the extreme values are computed dynamically at runtime.
588///
589/// Unfortunately, as Jiff grew bigger, ranged integers became more and more
590/// annoying. They have two fatal flaws as conceived above:
591///
592/// 1. If you "escape" outside of a ranged integer at any point, you lose the
593/// tracked min and max values. And thus you lose advantage of ranged integers
594/// in the first place.
595/// 2. Because of (1), it was exceptionally difficult to do any sort of clever
596/// representation-based optimizations. Since a ranged integer had its own
597/// representation, trying to do, e.g., bit-packing was effectively impossible.
598///
599/// There were some other downsides, although I didn't perceive them as fatal
600/// on their own (but they certaintly contributed to my decision to abdandon
601/// them):
602///
603/// * Since these were custom types and they were generic, literally none of
604/// them worked in a `const` context.
605/// * Whenever there was conditional control flow involving ranged integers,
606/// it was necessary to do strange contortions to get their internal min/max
607/// values to line up correctly.
608/// * As time pressed on, the panics surfaced by ranged integers were more and
609/// more likely to be as a result of "holding it wrong" and not because of any
610/// actual bugs in the arithmetic.
611///
612/// With all of that said, a datetime library has to be quite paranoid about
613/// the range of values it permits. And error messages really benefit from
614/// being able to encode information about the allowed range so that users know
615/// what is and isn't illegal. Hence, I settled on this lighter weight design
616/// that still encodes ranges as a static property of the program, but as
617/// associated types on a trait. And then that trait provides a few very
618/// primitive operations (like checking if an integer is in bounds) that
619/// produce an appropriate error message on failure.
620///
621/// Note also that this trait is specifically not implemented for `i128`.
622/// It's only implemented for `i8`, `i16`, `i32` and `i64`. This is because
623/// the `check` function accepts an `i64` as the type that can contain all
624/// possible values for _any_ range type. And then that value is checked before
625/// potentially being converted to a smaller primitive representation. If this
626/// trait supported `i128`, then one would want a `check` function where
627/// parameters get converted to `i128` and then their ranges are checked. I
628/// did not want this because of the extra costs associated with `i128`.
629///
630/// Moreover, Jiff is using `i128` in a vanishingly small number of locations.
631/// I don't think I'll ever be able to eliminate it entirely, but the number
632/// of locations is small enough that they are special cased and don't need to
633/// fit into this infrastructure.
634///
635/// Finally, a key property of this design is that error values carry no
636/// associated data. Instead, they are instantiated as implementations of
637/// this trait, and that implementation provides all of the information
638/// necessary to craft a half-way decent error message.
639///
640/// [jiff-range-history]: https://github.com/BurntSushi/jiff/issues/11
641pub trait Bounds: Sized {
642    /// A short human readable description of the values represented by these
643    /// bounds. This is used in error messages.
644    const WHAT: &'static str;
645
646    /// The minimum boundary value.
647    const MIN: Self::Primitive;
648
649    /// The maximum boundary value.
650    const MAX: Self::Primitive;
651
652    /// The primitive integer representation for this boundary type.
653    ///
654    /// This is generally the smallest primitive integer type that fits the
655    /// minimum and maximum allowed values.
656    type Primitive: Primitive;
657
658    /// The error type returned when a value is considered out of range for
659    /// this particular implementation.
660    ///
661    /// The intended usage is for this type to be an enum of single-field
662    /// variants. The field is meant to be an instantiation of `RawBoundsError`
663    /// with the type parameter set to `Self`.
664    ///
665    /// See [`BoundsError`] for an example.
666    type Error;
667
668    /// Create an error when a value is outside the bounds for this type.
669    fn error() -> Self::Error;
670
671    /// Converts the 64-bit integer provided into the primitive representation
672    /// of these bounds.
673    ///
674    /// # Errors
675    ///
676    /// This returns an error if the given integer does not fit in the bounds
677    /// prescribed by this trait implementation.
678    #[inline(always)]
679    fn check(n: impl Into<i64>) -> Result<Self::Primitive, Self::Error> {
680        let n = n.into();
681        if !(Self::MIN.as_i64() <= n && n <= Self::MAX.as_i64()) {
682            return Err(Self::error());
683        }
684        Ok(Self::Primitive::from_i64(n))
685    }
686
687    /// Checks whether the given integer, in the same primitive representation
688    /// as this boundary type, is in bounds.
689    ///
690    /// # Errors
691    ///
692    /// This returns an error if the given integer does not fit in the bounds
693    /// prescribed by this trait implementation.
694    #[inline(always)]
695    fn check_self(n: Self::Primitive) -> Result<Self::Primitive, Self::Error> {
696        if !(Self::MIN <= n && n <= Self::MAX) {
697            return Err(Self::error());
698        }
699        Ok(n)
700    }
701
702    /// Performs checked addition using this boundary type's primitive
703    /// representation.
704    ///
705    /// # Errors
706    ///
707    /// If the result exceeds the boundaries of the primitive type or of the
708    /// declared range for this type, then an error is returned.
709    #[inline(always)]
710    fn checked_add(
711        n1: Self::Primitive,
712        n2: Self::Primitive,
713    ) -> Result<Self::Primitive, Self::Error> {
714        Self::check_self(n1.checked_add(n2).ok_or_else(Self::error)?)
715    }
716
717    /// Performs checked multiplication using this boundary type's primitive
718    /// representation.
719    ///
720    /// # Errors
721    ///
722    /// If the result exceeds the boundaries of the primitive type or of the
723    /// declared range for this type, then an error is returned.
724    #[inline(always)]
725    fn checked_mul(
726        n1: Self::Primitive,
727        n2: Self::Primitive,
728    ) -> Result<Self::Primitive, Self::Error> {
729        Self::check_self(n1.checked_mul(n2).ok_or_else(Self::error)?)
730    }
731}
732
733/// An error type that encodes information from an implementation of
734/// [`Bounds`].
735///
736/// The information encoded is used to write an error message in response to a
737/// value being out of bounds.
738#[derive(Eq, PartialEq)]
739pub struct RawBoundsError<B>(core::marker::PhantomData<B>);
740
741impl<B> RawBoundsError<B> {
742    /// Create a new raw boundary error.
743    ///
744    /// It is intended for `B` to implement the `Bounds` trait. But this
745    /// constructor technically does not require it. However, the `Debug`
746    /// and `Display` impls on this type do require it.
747    #[inline]
748    pub const fn new() -> RawBoundsError<B> {
749        RawBoundsError(core::marker::PhantomData)
750    }
751}
752
753impl<B> Copy for RawBoundsError<B> {}
754
755impl<B> Clone for RawBoundsError<B> {
756    #[inline]
757    fn clone(&self) -> RawBoundsError<B> {
758        RawBoundsError::new()
759    }
760}
761
762impl<B, P> core::fmt::Debug for RawBoundsError<B>
763where
764    B: Bounds<Primitive = P>,
765    P: core::fmt::Debug,
766{
767    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
768        f.debug_struct("RawBoundsError")
769            .field("what", &B::WHAT)
770            .field("min", &B::MIN)
771            .field("max", &B::MAX)
772            .finish()
773    }
774}
775
776impl<B, P> core::fmt::Display for RawBoundsError<B>
777where
778    B: Bounds<Primitive = P>,
779    P: core::fmt::Display,
780{
781    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
782        write!(
783            f,
784            "parameter '{what}' is not in the required range of {min}..={max}",
785            what = B::WHAT,
786            min = B::MIN,
787            max = B::MAX,
788        )
789    }
790}
791
792#[cfg(feature = "defmt")]
793impl<B, P> defmt::Format for RawBoundsError<B>
794where
795    B: Bounds<Primitive = P>,
796    P: defmt::Format,
797{
798    fn format(&self, f: defmt::Formatter) {
799        defmt::write!(
800            f,
801            "RawBoundsError {{ what: {=str}, min: {}, max: {} }}",
802            B::WHAT,
803            B::MIN,
804            B::MAX
805        );
806    }
807}
808
809/// The error type used by the trait implementations of `Bounds` in this crate.
810///
811/// Callers can think of this as a single unifying error value that combines
812/// all implementations of `Bounds` in this crate. Internally, it's an enum
813/// with each variant corresponding to a single implementation of `Bounds`.
814/// Each variant has a field containing a zero-sized `RawBoundsError<B>`,
815/// where `B` is the type implementing `Bounds`.
816#[derive(Clone, Copy, Debug, Eq, PartialEq)]
817#[cfg_attr(feature = "defmt", derive(defmt::Format))]
818pub struct BoundsError {
819    kind: BoundsErrorKind,
820}
821
822impl BoundsError {
823    pub(crate) const fn into_range_error(self) -> RangeError {
824        RangeError { kind: RangeErrorKind::Bounds(self) }
825    }
826}
827
828impl core::fmt::Display for BoundsError {
829    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
830        self.kind.fmt(f)
831    }
832}
833
834/// An error type that combines `BoundsError` with other custom error variants.
835///
836/// For example, a range error on the days of the month when constructing a
837/// date would ideally include the corresponding year and month. Otherwise the
838/// error message is likely to be much less useful than it could be.
839#[derive(Clone, Copy, Debug, Eq, PartialEq)]
840#[cfg_attr(feature = "defmt", derive(defmt::Format))]
841pub struct RangeError {
842    kind: RangeErrorKind,
843}
844
845impl From<BoundsError> for RangeError {
846    fn from(err: BoundsError) -> RangeError {
847        err.into_range_error()
848    }
849}
850
851impl From<SpecialBoundsError> for RangeError {
852    fn from(err: SpecialBoundsError) -> RangeError {
853        err.into_range_error()
854    }
855}
856
857impl core::fmt::Display for RangeError {
858    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
859        match self.kind {
860            RangeErrorKind::Bounds(ref err) => err.fmt(f),
861            RangeErrorKind::Special(ref err) => err.fmt(f),
862        }
863    }
864}
865
866#[cfg(feature = "std")]
867impl std::error::Error for RangeError {}
868
869#[derive(Clone, Copy, Debug, Eq, PartialEq)]
870#[cfg_attr(feature = "defmt", derive(defmt::Format))]
871enum RangeErrorKind {
872    Bounds(BoundsError),
873    Special(SpecialBoundsError),
874}
875
876#[derive(Clone, Copy, Debug, Eq, PartialEq)]
877#[cfg_attr(feature = "defmt", derive(defmt::Format))]
878pub(crate) enum SpecialBoundsError {
879    DateInvalidDay { year: i16, month: i8 },
880    DateInvalidDayOfYear { year: i16 },
881    UnixEpochNanoseconds,
882}
883
884impl SpecialBoundsError {
885    pub(crate) const fn into_range_error(self) -> RangeError {
886        RangeError { kind: RangeErrorKind::Special(self) }
887    }
888}
889
890impl core::fmt::Display for SpecialBoundsError {
891    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
892        use self::SpecialBoundsError::*;
893
894        match *self {
895            DateInvalidDay { year, month } => write!(
896                f,
897                "parameter 'day' for `{year:04}-{month:02}` is invalid, \
898                 must be in range `1..={max_day}`",
899                max_day = crate::civil::days_in_month(year, month),
900            ),
901            DateInvalidDayOfYear { year } => write!(
902                f,
903                "number of days for `{year:04}` is invalid, \
904                 must be in range `1..={max_day}`",
905                max_day = crate::civil::days_in_year(year),
906            ),
907            UnixEpochNanoseconds => write!(
908                f,
909                "parameter 'Unix timestamp nanoseconds' \
910                 is not in the required range of {min}..={max}",
911                min = UnixEpochMicroseconds::MIN as i128
912                    * (c::NANOS_PER_MICRO as i128),
913                max = UnixEpochMicroseconds::MAX as i128
914                    * (c::NANOS_PER_MICRO as i128),
915            ),
916        }
917    }
918}
919
920/// Provides routines usable in a `const` context for checking boundary values.
921///
922/// These routines return a `RawBoundsError` and thus work with any type that
923/// implements the `Bounds` trait.
924#[allow(missing_docs)]
925pub mod const_check {
926    use super::{Bounds, RawBoundsError};
927
928    #[inline(always)]
929    pub const fn i8<B>(n: i64) -> Result<i8, RawBoundsError<B>>
930    where
931        B: Bounds<Primitive = i8>,
932    {
933        if !((B::MIN as i64) <= n && n <= (B::MAX as i64)) {
934            return Err(RawBoundsError::new());
935        }
936        Ok(n as i8)
937    }
938
939    #[inline(always)]
940    pub const fn i16<B>(n: i64) -> Result<i16, RawBoundsError<B>>
941    where
942        B: Bounds<Primitive = i16>,
943    {
944        if !((B::MIN as i64) <= n && n <= (B::MAX as i64)) {
945            return Err(RawBoundsError::new());
946        }
947        Ok(n as i16)
948    }
949
950    #[inline(always)]
951    pub const fn i32<B>(n: i64) -> Result<i32, RawBoundsError<B>>
952    where
953        B: Bounds<Primitive = i32>,
954    {
955        if !((B::MIN as i64) <= n && n <= (B::MAX as i64)) {
956            return Err(RawBoundsError::new());
957        }
958        Ok(n as i32)
959    }
960
961    #[inline(always)]
962    pub const fn i64<B>(n: i64) -> Result<i64, RawBoundsError<B>>
963    where
964        B: Bounds<Primitive = i64>,
965    {
966        if !(B::MIN <= n && n <= B::MAX) {
967            return Err(RawBoundsError::new());
968        }
969        Ok(n)
970    }
971}
972
973/// Provides routines usable in a `const` context for checked arithmetic.
974///
975/// These routines return a `RawBoundsError` and thus work with any type that
976/// implements the `Bounds` trait.
977#[allow(missing_docs)]
978pub mod const_checked_add {
979    use super::{Bounds, RawBoundsError};
980
981    #[inline(always)]
982    pub const fn i8<B>(n1: i8, n2: i8) -> Result<i8, RawBoundsError<B>>
983    where
984        B: Bounds<Primitive = i8>,
985    {
986        let sum = match n1.checked_add(n2) {
987            Some(sum) => sum,
988            None => return Err(RawBoundsError::new()),
989        };
990        super::const_check::i8(sum as i64)
991    }
992
993    #[inline(always)]
994    pub const fn i16<B>(n1: i16, n2: i16) -> Result<i16, RawBoundsError<B>>
995    where
996        B: Bounds<Primitive = i16>,
997    {
998        let sum = match n1.checked_add(n2) {
999            Some(sum) => sum,
1000            None => return Err(RawBoundsError::new()),
1001        };
1002        super::const_check::i16(sum as i64)
1003    }
1004
1005    #[inline(always)]
1006    pub const fn i32<B>(n1: i32, n2: i32) -> Result<i32, RawBoundsError<B>>
1007    where
1008        B: Bounds<Primitive = i32>,
1009    {
1010        let sum = match n1.checked_add(n2) {
1011            Some(sum) => sum,
1012            None => return Err(RawBoundsError::new()),
1013        };
1014        super::const_check::i32(sum as i64)
1015    }
1016
1017    #[inline(always)]
1018    pub const fn i64<B>(n1: i64, n2: i64) -> Result<i64, RawBoundsError<B>>
1019    where
1020        B: Bounds<Primitive = i64>,
1021    {
1022        let sum = match n1.checked_add(n2) {
1023            Some(sum) => sum,
1024            None => return Err(RawBoundsError::new()),
1025        };
1026        super::const_check::i64(sum)
1027    }
1028}
1029
1030/// A representation of a numeric sign.
1031///
1032/// Its `Display` impl emits the ASCII minus sign, `-`, when this is negative.
1033/// It emits the empty string in all other cases.
1034#[derive(
1035    Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord,
1036)]
1037#[repr(i8)]
1038#[allow(missing_docs)]
1039pub enum Sign {
1040    #[default]
1041    Zero = 0,
1042    Positive = 1,
1043    Negative = -1,
1044}
1045
1046impl Sign {
1047    /// Returns true when the sign is `Sign::Zero`.
1048    #[inline]
1049    pub const fn is_zero(self) -> bool {
1050        matches!(self, Sign::Zero)
1051    }
1052
1053    /// Returns true when the sign is `Sign::Positive`.
1054    #[inline]
1055    pub const fn is_positive(self) -> bool {
1056        matches!(self, Sign::Positive)
1057    }
1058
1059    /// Returns true when the sign is `Sign::Negative`.
1060    #[inline]
1061    pub const fn is_negative(self) -> bool {
1062        matches!(self, Sign::Negative)
1063    }
1064
1065    /// Returns the sign as an `i8`.
1066    ///
1067    /// This is guaranteed to be `-1`, `0` or `1`.
1068    #[inline]
1069    pub const fn signum(self) -> i8 {
1070        self.as_i8()
1071    }
1072
1073    /// Returns the sign as an `i8`.
1074    ///
1075    /// This is guaranteed to be `-1`, `0` or `1`.
1076    #[inline]
1077    pub const fn as_i8(self) -> i8 {
1078        self as i8
1079    }
1080
1081    /// Returns the sign as an `i16`.
1082    ///
1083    /// This is guaranteed to be `-1`, `0` or `1`.
1084    #[inline]
1085    pub const fn as_i16(self) -> i16 {
1086        self as i16
1087    }
1088
1089    /// Returns the sign as an `i32`.
1090    ///
1091    /// This is guaranteed to be `-1`, `0` or `1`.
1092    #[inline]
1093    pub const fn as_i32(self) -> i32 {
1094        self as i32
1095    }
1096
1097    /// Returns the sign as an `i64`.
1098    ///
1099    /// This is guaranteed to be `-1`, `0` or `1`.
1100    #[inline]
1101    pub const fn as_i64(self) -> i64 {
1102        self as i64
1103    }
1104
1105    /// Returns the sign as an `i128`.
1106    ///
1107    /// This is guaranteed to be `-1`, `0` or `1`.
1108    #[inline]
1109    pub const fn as_i128(self) -> i128 {
1110        self as i128
1111    }
1112
1113    /// Returns a `Sign` as a result of comparing two values.
1114    ///
1115    /// * When `t1 < t2`, returns `Sign::Negative`.
1116    /// * When `t1 == t2`, returns `Sign::Zero`.
1117    /// * When `t1 > t2`, returns `Sign::Positive`.
1118    #[inline]
1119    pub fn from_ordinals<T: Ord>(t1: T, t2: T) -> Sign {
1120        use core::cmp::Ordering::*;
1121        match t1.cmp(&t2) {
1122            Less => Sign::Negative,
1123            Equal => Sign::Zero,
1124            Greater => Sign::Positive,
1125        }
1126    }
1127}
1128
1129impl core::fmt::Display for Sign {
1130    #[inline]
1131    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1132        if self.is_negative() {
1133            f.write_str("-")
1134        } else {
1135            Ok(())
1136        }
1137    }
1138}
1139
1140impl core::ops::Neg for Sign {
1141    type Output = Sign;
1142
1143    #[inline]
1144    fn neg(self) -> Sign {
1145        match self {
1146            Sign::Positive => Sign::Negative,
1147            Sign::Zero => Sign::Zero,
1148            Sign::Negative => Sign::Positive,
1149        }
1150    }
1151}
1152
1153impl From<i8> for Sign {
1154    #[inline]
1155    fn from(n: i8) -> Sign {
1156        Sign::from(i64::from(n))
1157    }
1158}
1159
1160impl From<i16> for Sign {
1161    #[inline]
1162    fn from(n: i16) -> Sign {
1163        Sign::from(i64::from(n))
1164    }
1165}
1166
1167impl From<i32> for Sign {
1168    #[inline]
1169    fn from(n: i32) -> Sign {
1170        Sign::from(i64::from(n))
1171    }
1172}
1173
1174impl From<i64> for Sign {
1175    #[inline]
1176    fn from(n: i64) -> Sign {
1177        if n == 0 {
1178            Sign::Zero
1179        } else if n > 0 {
1180            Sign::Positive
1181        } else {
1182            Sign::Negative
1183        }
1184    }
1185}
1186
1187impl From<i128> for Sign {
1188    #[inline]
1189    fn from(n: i128) -> Sign {
1190        if n == 0 {
1191            Sign::Zero
1192        } else if n > 0 {
1193            Sign::Positive
1194        } else {
1195            Sign::Negative
1196        }
1197    }
1198}
1199
1200/// This considers `NaN` values as having a zero sign.
1201///
1202/// In general, Jiff having a `NaN` value in memory is a bug.
1203impl From<f64> for Sign {
1204    #[inline]
1205    fn from(n: f64) -> Sign {
1206        use core::num::FpCategory::*;
1207
1208        // This is a little odd, but we want +/- 0 to
1209        // always have a sign of zero, so as to be consistent
1210        // with how we deal with signed integers.
1211        //
1212        // As for NaN... It should generally be a bug if
1213        // Jiff ever materializes a NaN. Notably, I do not
1214        // believe there are any APIs in which a float is
1215        // given from the caller. Jiff only ever uses them
1216        // internally or returns them. So if we get a NaN,
1217        // it's on us. If we do, just assign it a zero sign?
1218        if matches!(n.classify(), Nan | Zero) {
1219            Sign::Zero
1220        } else if n.is_sign_positive() {
1221            Sign::Positive
1222        } else {
1223            Sign::Negative
1224        }
1225    }
1226}
1227
1228impl core::ops::Mul<Sign> for Sign {
1229    type Output = Sign;
1230
1231    #[inline]
1232    fn mul(self, rhs: Sign) -> Sign {
1233        match (self, rhs) {
1234            (Sign::Zero, _) | (_, Sign::Zero) => Sign::Zero,
1235            (Sign::Positive, Sign::Positive) => Sign::Positive,
1236            (Sign::Negative, Sign::Negative) => Sign::Positive,
1237            (Sign::Positive, Sign::Negative) => Sign::Negative,
1238            (Sign::Negative, Sign::Positive) => Sign::Negative,
1239        }
1240    }
1241}
1242
1243impl core::ops::Mul<i8> for Sign {
1244    type Output = i8;
1245
1246    #[inline]
1247    fn mul(self, n: i8) -> i8 {
1248        self.as_i8() * n
1249    }
1250}
1251
1252impl core::ops::Mul<Sign> for i8 {
1253    type Output = i8;
1254
1255    #[inline]
1256    fn mul(self, n: Sign) -> i8 {
1257        self * n.as_i8()
1258    }
1259}
1260
1261impl core::ops::Mul<i16> for Sign {
1262    type Output = i16;
1263
1264    #[inline]
1265    fn mul(self, n: i16) -> i16 {
1266        self.as_i16() * n
1267    }
1268}
1269
1270impl core::ops::Mul<Sign> for i16 {
1271    type Output = i16;
1272
1273    #[inline]
1274    fn mul(self, n: Sign) -> i16 {
1275        self * n.as_i16()
1276    }
1277}
1278
1279impl core::ops::Mul<i32> for Sign {
1280    type Output = i32;
1281
1282    #[inline]
1283    fn mul(self, n: i32) -> i32 {
1284        self.as_i32() * n
1285    }
1286}
1287
1288impl core::ops::Mul<Sign> for i32 {
1289    type Output = i32;
1290
1291    #[inline]
1292    fn mul(self, n: Sign) -> i32 {
1293        self * n.as_i32()
1294    }
1295}
1296
1297impl core::ops::Mul<i64> for Sign {
1298    type Output = i64;
1299
1300    #[inline]
1301    fn mul(self, n: i64) -> i64 {
1302        self.as_i64() * n
1303    }
1304}
1305
1306impl core::ops::Mul<Sign> for i64 {
1307    type Output = i64;
1308
1309    #[inline]
1310    fn mul(self, n: Sign) -> i64 {
1311        self * n.as_i64()
1312    }
1313}
1314
1315impl core::ops::Mul<i128> for Sign {
1316    type Output = i128;
1317
1318    #[inline]
1319    fn mul(self, n: i128) -> i128 {
1320        self.as_i128() * n
1321    }
1322}
1323
1324impl core::ops::Mul<Sign> for i128 {
1325    type Output = i128;
1326
1327    #[inline]
1328    fn mul(self, n: Sign) -> i128 {
1329        self * n.as_i128()
1330    }
1331}
1332
1333/// Computes the next multiple of `rhs` that is greater than or equal to `lhs`.
1334///
1335/// Taken from:
1336/// https://github.com/rust-lang/rust/blob/eff958c59e8c07ba0515e164b825c9001b242294/library/core/src/num/int_macros.rs
1337const fn next_multiple_of(lhs: i64, rhs: i64) -> i64 {
1338    // This would otherwise fail when calculating `r` when self == T::MIN.
1339    if rhs == -1 {
1340        return lhs;
1341    }
1342
1343    let r = lhs % rhs;
1344    let m = if (r > 0 && rhs < 0) || (r < 0 && rhs > 0) { r + rhs } else { r };
1345    if m == 0 {
1346        lhs
1347    } else {
1348        lhs + (rhs - m)
1349    }
1350}