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}