Skip to main content

jiff/util/
round.rs

1use crate::{
2    error::{
3        unit::{MustDivide, UnitConfigError},
4        util::RoundingIncrementError,
5        Error, ErrorContext,
6    },
7    util::b,
8    SignedDuration, Unit,
9};
10
11/// A representation of a rounding increment in a particular unit.
12///
13/// This implements the core rounding interface of Jiff, shared across all
14/// types. The constructors on `Increment` know which units *and* increments
15/// are valid for a particular rounding configuration. For example, only
16/// `Increment::for_span` supports any `unit` value. All other constructors
17/// have some kind of limitation (e.g., you can't round a date to the nearest
18/// month).
19///
20/// For most cases, rounding is done via `Increment::round`. This takes a
21/// quantity in a number of nanoseconds and rounds it to the nearest increment
22/// (after converting the increment to nanoseconds as well, based on the unit
23/// provided). This API makes it difficult for callers to get rounding wrong.
24/// They just need to use the right constructor and provide a quantity in units
25/// of nanoseconds.
26///
27/// For lower level needs, one can use `RoundMode::round_by_duration` directly.
28/// This doesn't know about any units other than nanoseconds. It's useful in
29/// contexts when dealing with variable length units and there are no rules
30/// for what is a valid increment.
31#[derive(Clone, Copy, Debug)]
32pub(crate) struct Increment {
33    unit: Unit,
34    value: i32,
35}
36
37impl Increment {
38    /// Return a rounding increment suitable for use when rounding a `Span`.
39    pub(crate) fn for_span(
40        unit: Unit,
41        value: i64,
42    ) -> Result<Increment, Error> {
43        // Indexed by `Unit`.
44        static LIMITS: &[MustDivide] = &[
45            MustDivide::NanosPerMicro,
46            MustDivide::MicrosPerMilli,
47            MustDivide::MillisPerSec,
48            MustDivide::SecsPerMin,
49            MustDivide::MinsPerHour,
50            MustDivide::HoursPerCivilDay,
51        ];
52
53        // We allow any kind of increment for calendar units, but for time
54        // units, they have to divide evenly into the next highest unit (and
55        // also be less than that). The reason for this is that calendar
56        // units vary, where as for time units, given a balanced span, you
57        // know that time units will always spill over into days so that
58        // hours/minutes/... will never exceed 24/60/...
59        if unit < Unit::Day {
60            Increment::for_limits(unit, value, LIMITS)
61                .context(RoundingIncrementError::ForSpan)
62        } else {
63            // For calendar units, just verify that the increment is in the
64            // correct range.
65            let value = b::Increment32::check(value)
66                .context(RoundingIncrementError::ForSpan)?;
67            Ok(Increment { unit, value })
68        }
69    }
70
71    /// Return a rounding increment suitable for use when rounding a
72    /// `SignedDuration`.
73    pub(crate) fn for_signed_duration(
74        unit: Unit,
75        value: i64,
76    ) -> Result<Increment, Error> {
77        if unit > Unit::Hour {
78            return Err(Error::from(
79                UnitConfigError::SignedDurationCalendarUnit { smallest: unit },
80            ));
81        }
82
83        // For signed durations, we allow any increment that is within range.
84        let value = b::Increment32::check(value)
85            .context(RoundingIncrementError::ForSignedDuration)?;
86        Ok(Increment { unit, value })
87    }
88
89    /// Return a rounding increment suitable for use when rounding a
90    /// `tz::Offset`.
91    pub(crate) fn for_offset(
92        unit: Unit,
93        value: i64,
94    ) -> Result<Increment, Error> {
95        if !(Unit::Second <= unit && unit <= Unit::Hour) {
96            return Err(Error::from(UnitConfigError::TimeZoneOffset {
97                given: unit,
98            }));
99        }
100
101        // For time zone offsets, we allow any increment that is within range.
102        let value = b::Increment32::check(value)
103            .context(RoundingIncrementError::ForOffset)?;
104        Ok(Increment { unit, value })
105    }
106
107    /// Return a rounding increment suitable for use when rounding a
108    /// `civil::DateTime` or a `Zoned`.
109    pub(crate) fn for_datetime(
110        unit: Unit,
111        value: i64,
112    ) -> Result<Increment, Error> {
113        // Indexed by `Unit`.
114        static LIMITS: &[MustDivide] = &[
115            MustDivide::NanosPerMicro,
116            MustDivide::MicrosPerMilli,
117            MustDivide::MillisPerSec,
118            MustDivide::SecsPerMin,
119            MustDivide::MinsPerHour,
120            MustDivide::HoursPerCivilDay,
121            MustDivide::Days,
122        ];
123        Increment::for_limits(unit, value, LIMITS)
124            .context(RoundingIncrementError::ForDateTime)
125    }
126
127    /// Return a rounding increment suitable for use when rounding a
128    /// `civil::Time`.
129    pub(crate) fn for_time(
130        unit: Unit,
131        value: i64,
132    ) -> Result<Increment, Error> {
133        // Indexed by `Unit`.
134        static LIMITS: &[MustDivide] = &[
135            MustDivide::NanosPerMicro,
136            MustDivide::MicrosPerMilli,
137            MustDivide::MillisPerSec,
138            MustDivide::SecsPerMin,
139            MustDivide::MinsPerHour,
140            MustDivide::HoursPerCivilDay,
141        ];
142        Increment::for_limits(unit, value, LIMITS)
143            .context(RoundingIncrementError::ForTime)
144    }
145
146    /// Return a rounding increment suitable for use when rounding a
147    /// `civil::Timestamp`.
148    pub(crate) fn for_timestamp(
149        unit: Unit,
150        value: i64,
151    ) -> Result<Increment, Error> {
152        // Indexed by `Unit`.
153        static MAXIMUMS: &[MustDivide] = &[
154            MustDivide::NanosPerCivilDay,
155            MustDivide::MicrosPerCivilDay,
156            MustDivide::MillisPerCivilDay,
157            MustDivide::SecsPerCivilDay,
158            MustDivide::MinsPerCivilDay,
159            MustDivide::HoursPerCivilDay,
160        ];
161        Increment::for_maximums(unit, value, MAXIMUMS)
162            .context(RoundingIncrementError::ForTimestamp)
163    }
164
165    /// Rounds the given quantity to the nearest increment value (in terms of
166    /// the units given to this increment's constructor). Rounding down or up
167    /// is determined by the mode given.
168    pub(crate) fn round(
169        &self,
170        mode: RoundMode,
171        quantity: SignedDuration,
172    ) -> Result<SignedDuration, Error> {
173        // OK because the max for the number of nanoseconds in a unit
174        // is weeks at `604_800_000_000_000` and `increment` cannot be
175        // any larger than `1_000_000_000`. The multiplication of these two
176        // values does not exceed `SignedDuration::MAX`.
177        let increment = self.value() * self.unit().duration();
178        mode.round_by_duration(quantity, increment)
179    }
180
181    /// Returns this increment's unit.
182    ///
183    /// The increment value is always in terms of this unit.
184    pub(crate) fn unit(&self) -> Unit {
185        self.unit
186    }
187
188    /// Returns this increment's value.
189    ///
190    /// e.g., "round to the nearest 15th minute" is expressed with an increment
191    /// value of `15` and a unit of `Unit::Minute`.
192    ///
193    /// The value returned is guaranteed to be in the range
194    /// `1..=1_000_000_000`.
195    pub(crate) fn value(&self) -> i32 {
196        self.value
197    }
198}
199
200/// Lower level constructors that require the caller to know the precise
201/// limits or maximums. These should generally stay unexported.
202impl Increment {
203    /// Validate the `increment` value for the given `unit` and limits.
204    ///
205    /// Specifically, this ensures that rounding to the given `unit` is
206    /// supported and that the specific value is *less than* to the
207    /// corresponding limit.
208    fn for_limits(
209        unit: Unit,
210        value: i64,
211        limits: &[MustDivide],
212    ) -> Result<Increment, Error> {
213        let Some(&must_divide) = limits.get(unit as usize) else {
214            return Err(Error::from(
215                UnitConfigError::RoundToUnitUnsupported { unit },
216            ));
217        };
218
219        let value32 = b::Increment32::check(value)?;
220        let must_divide_i64 = must_divide.as_i64();
221        if value < must_divide_i64 && must_divide_i64 % value == 0 {
222            return Ok(Increment { unit, value: value32 });
223        }
224        Err(Error::from(UnitConfigError::IncrementDivide {
225            unit,
226            must_divide,
227        }))
228    }
229
230    /// Validate the `increment` value for the given `unit` and maximums.
231    ///
232    /// Specifically, this ensures that rounding to the given `unit` is
233    /// supported and that the specific value is *less than or equal* to the
234    /// corresponding maximum.
235    fn for_maximums(
236        unit: Unit,
237        value: i64,
238        maximums: &[MustDivide],
239    ) -> Result<Increment, Error> {
240        let Some(&must_divide) = maximums.get(unit as usize) else {
241            return Err(Error::from(
242                UnitConfigError::RoundToUnitUnsupported { unit },
243            ));
244        };
245
246        let value32 = b::Increment32::check(value)?;
247        let must_divide_i64 = must_divide.as_i64();
248        if value <= must_divide_i64 && must_divide_i64 % value == 0 {
249            return Ok(Increment { unit, value: value32 });
250        }
251        Err(Error::from(UnitConfigError::IncrementDivide {
252            unit,
253            must_divide,
254        }))
255    }
256}
257
258/// The mode for dealing with the remainder when rounding datetimes or spans.
259///
260/// This is used in APIs like [`Span::round`](crate::Span::round) for rounding
261/// spans, and APIs like [`Zoned::round`](crate::Zoned::round) for rounding
262/// datetimes.
263///
264/// In the documentation for each variant, we refer to concepts like the
265/// "smallest" unit and the "rounding increment." These are best described
266/// in the documentation for what you're rounding. For example,
267/// [`SpanRound::smallest`](crate::SpanRound::smallest)
268/// and [`SpanRound::increment`](crate::SpanRound::increment).
269///
270/// # Example
271///
272/// This shows how to round a span with a different rounding mode than the
273/// default:
274///
275/// ```
276/// use jiff::{RoundMode, SpanRound, ToSpan, Unit};
277///
278/// // The default rounds like how you were taught in school:
279/// assert_eq!(
280///     1.hour().minutes(59).round(Unit::Hour)?,
281///     2.hours().fieldwise(),
282/// );
283/// // But we can change the mode, e.g., truncation:
284/// let options = SpanRound::new().smallest(Unit::Hour).mode(RoundMode::Trunc);
285/// assert_eq!(
286///     1.hour().minutes(59).round(options)?,
287///     1.hour().fieldwise(),
288/// );
289///
290/// # Ok::<(), Box<dyn std::error::Error>>(())
291/// ```
292#[non_exhaustive]
293#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
294#[cfg_attr(feature = "defmt", derive(defmt::Format))]
295pub enum RoundMode {
296    /// Rounds toward positive infinity.
297    ///
298    /// For negative spans and datetimes, this option will make the absolute
299    /// value smaller, which could be unexpected. To round away from zero, use
300    /// `Expand`.
301    Ceil,
302    /// Rounds toward negative infinity.
303    ///
304    /// This mode acts like `Trunc` for positive spans and datetimes, but
305    /// for negative values, it will make the absolute value larger, which
306    /// could be unexpected. To round towards zero, use `Trunc`.
307    Floor,
308    /// Rounds away from zero like `Ceil` for positive spans and datetimes,
309    /// and like `Floor` for negative spans and datetimes.
310    Expand,
311    /// Rounds toward zero, chopping off any fractional part of a unit.
312    ///
313    /// This is the default when rounding spans returned from
314    /// datetime arithmetic. (But it is not the default for
315    /// [`Span::round`](crate::Span::round).)
316    Trunc,
317    /// Rounds to the nearest allowed value like `HalfExpand`, but when there
318    /// is a tie, round towards positive infinity like `Ceil`.
319    HalfCeil,
320    /// Rounds to the nearest allowed value like `HalfExpand`, but when there
321    /// is a tie, round towards negative infinity like `Floor`.
322    HalfFloor,
323    /// Rounds to the nearest value allowed by the rounding increment and the
324    /// smallest unit. When there is a tie, round away from zero like `Ceil`
325    /// for positive spans and datetimes and like `Floor` for negative spans
326    /// and datetimes.
327    ///
328    /// This corresponds to how rounding is often taught in school.
329    ///
330    /// This is the default for rounding spans and datetimes.
331    HalfExpand,
332    /// Rounds to the nearest allowed value like `HalfExpand`, but when there
333    /// is a tie, round towards zero like `Trunc`.
334    HalfTrunc,
335    /// Rounds to the nearest allowed value like `HalfExpand`, but when there
336    /// is a tie, round towards the value that is an even multiple of the
337    /// rounding increment. For example, with a rounding increment of `3`,
338    /// the number `10` would round up to `12` instead of down to `9`, because
339    /// `12` is an even multiple of `3`, where as `9` is is an odd multiple.
340    HalfEven,
341}
342
343impl RoundMode {
344    /// Round `quantity` to the nearest `increment` using this rounding mode.
345    ///
346    /// If this the rounding result would overflow `SignedDuration`, then an
347    /// error is returned.
348    ///
349    /// Callers should generally prefer higher level APIs. But this one is
350    /// unavoidable when the increment isn't tied to an invariant length and
351    /// can vary.
352    ///
353    /// # Panics
354    ///
355    /// Callers must ensure that `increment` has a number of nanoseconds
356    /// greater than or equal to `1`.
357    pub(crate) fn round_by_duration(
358        self,
359        quantity: SignedDuration,
360        increment: SignedDuration,
361    ) -> Result<SignedDuration, Error> {
362        let quantity = quantity.as_nanos();
363        let increment = increment.as_nanos();
364        assert!(increment >= 1);
365        let rounded = self.round_by_i128(quantity, increment);
366        // This can fail in the case where `quantity` is somehow rounded to a
367        // value above/below a signed duration's max/min. This cannot *usually*
368        // happen in practice because our `quantity` is typically limited by
369        // the maximum Jiff datetime span. Even spanning from -9999 to 9999, a
370        // 96-bit integer number of nanoseconds still can't cause an overflow.
371        // Moreover, `increment` is usually capped to `1_000_000_000`. The only
372        // case where it isn't is when we're rounding based on variable length
373        // units (in which case, this lower level rounding routine is called
374        // directly). For example, when rounding to years with an increment
375        // (set by the user) to `15_000`. That will get translated to 15,000
376        // years *in nanoseconds*.
377        //
378        // Even still, the complete span of time supported by Jiff can
379        // comfortably fit into a `SignedDuration`. And because of the limit
380        // placed on `increment`, I don't believe this error can ever actually
381        // occur.
382        SignedDuration::try_from_nanos_i128(rounded)
383            .ok_or_else(|| Error::from(b::SignedDurationSeconds::error()))
384    }
385
386    /// The internal API that does the actual rounding.
387    ///
388    /// This does not error on overflow because callers can never use values
389    /// close to the `i128` limits. If an overflow would otherwise occur due to
390    /// rounding, then it saturates instead of panicking or returning an error.
391    ///
392    /// I wish we could do modulus on `SignedDuration` directly. Then we
393    /// wouldn't need 128-bit arithmetic at all. But I looked into making
394    /// `SignedDuration % SignedDuration` work, and it was quite involved? So I
395    /// guess we should just let the compiler handle it for `i128`. We'd also
396    /// need division.
397    ///
398    /// # Panics
399    ///
400    /// When `increment < 1`.
401    fn round_by_i128(self, quantity: i128, increment: i128) -> i128 {
402        assert!(increment >= 1);
403        // ref: https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement
404        let mode = self;
405        let mut quotient = quantity / increment;
406        let remainder = quantity % increment;
407        if remainder == 0 {
408            return quantity;
409        }
410        let sign = if remainder < 0 { -1 } else { 1 };
411        let tiebreaker = (remainder * 2).abs();
412        let tie = tiebreaker == increment;
413        let expand_is_nearer = tiebreaker > increment;
414        // ref: https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement
415        match mode {
416            RoundMode::Ceil => {
417                if sign > 0 {
418                    quotient += sign;
419                }
420            }
421            RoundMode::Floor => {
422                if sign < 0 {
423                    quotient += sign;
424                }
425            }
426            RoundMode::Expand => {
427                quotient += sign;
428            }
429            RoundMode::Trunc => {}
430            RoundMode::HalfCeil => {
431                if expand_is_nearer || (tie && sign > 0) {
432                    quotient += sign;
433                }
434            }
435            RoundMode::HalfFloor => {
436                if expand_is_nearer || (tie && sign < 0) {
437                    quotient += sign;
438                }
439            }
440            RoundMode::HalfExpand => {
441                if expand_is_nearer || tie {
442                    quotient += sign;
443                }
444            }
445            RoundMode::HalfTrunc => {
446                if expand_is_nearer {
447                    quotient += sign;
448                }
449            }
450            RoundMode::HalfEven => {
451                if expand_is_nearer || (tie && quotient.rem_euclid(2) == 1) {
452                    quotient += sign;
453                }
454            }
455        }
456        // We use saturating arithmetic here because this can overflow when
457        // `quantity` is the max value. Since we're rounding, we just refuse to
458        // go over the maximum. This can't happen in practice because this is
459        // a private API and all paths to this API enforce that we are never
460        // going to round over the `i128` maximum.
461        quotient.saturating_mul(increment)
462    }
463}
464
465#[cfg(test)]
466mod tests {
467    use super::*;
468
469    #[test]
470    fn for_span() {
471        let get = |unit, value| Increment::for_span(unit, value);
472
473        assert!(get(Unit::Nanosecond, 1).is_ok());
474        assert!(get(Unit::Nanosecond, 500).is_ok());
475        assert!(get(Unit::Nanosecond, -1).is_err());
476        assert!(get(Unit::Nanosecond, 0).is_err());
477        assert!(get(Unit::Nanosecond, 7).is_err());
478        assert!(get(Unit::Nanosecond, 1_000).is_err());
479        assert!(get(Unit::Nanosecond, i64::MIN).is_err());
480        assert!(get(Unit::Nanosecond, i64::MAX).is_err());
481
482        assert!(get(Unit::Microsecond, 1).is_ok());
483        assert!(get(Unit::Microsecond, 500).is_ok());
484        assert!(get(Unit::Microsecond, -1).is_err());
485        assert!(get(Unit::Microsecond, 0).is_err());
486        assert!(get(Unit::Microsecond, 7).is_err());
487        assert!(get(Unit::Microsecond, 1_000).is_err());
488        assert!(get(Unit::Microsecond, i64::MIN).is_err());
489        assert!(get(Unit::Microsecond, i64::MAX).is_err());
490
491        assert!(get(Unit::Millisecond, 1).is_ok());
492        assert!(get(Unit::Millisecond, 500).is_ok());
493        assert!(get(Unit::Millisecond, -1).is_err());
494        assert!(get(Unit::Millisecond, 0).is_err());
495        assert!(get(Unit::Millisecond, 7).is_err());
496        assert!(get(Unit::Millisecond, 1_000).is_err());
497        assert!(get(Unit::Millisecond, i64::MIN).is_err());
498        assert!(get(Unit::Millisecond, i64::MAX).is_err());
499
500        assert!(get(Unit::Second, 1).is_ok());
501        assert!(get(Unit::Second, 15).is_ok());
502        assert!(get(Unit::Second, -1).is_err());
503        assert!(get(Unit::Second, 0).is_err());
504        assert!(get(Unit::Second, 7).is_err());
505        assert!(get(Unit::Second, 1_000).is_err());
506        assert!(get(Unit::Second, i64::MIN).is_err());
507        assert!(get(Unit::Second, i64::MAX).is_err());
508
509        assert!(get(Unit::Minute, 1).is_ok());
510        assert!(get(Unit::Minute, 15).is_ok());
511        assert!(get(Unit::Minute, -1).is_err());
512        assert!(get(Unit::Minute, 0).is_err());
513        assert!(get(Unit::Minute, 7).is_err());
514        assert!(get(Unit::Minute, 1_000).is_err());
515        assert!(get(Unit::Minute, i64::MIN).is_err());
516        assert!(get(Unit::Minute, i64::MAX).is_err());
517
518        assert!(get(Unit::Hour, 1).is_ok());
519        assert!(get(Unit::Hour, 6).is_ok());
520        assert!(get(Unit::Hour, 12).is_ok());
521        assert!(get(Unit::Hour, -1).is_err());
522        assert!(get(Unit::Hour, 0).is_err());
523        assert!(get(Unit::Hour, 15).is_err());
524        assert!(get(Unit::Hour, 18).is_err());
525        assert!(get(Unit::Hour, 23).is_err());
526        assert!(get(Unit::Hour, 24).is_err());
527        assert!(get(Unit::Hour, i64::MIN).is_err());
528        assert!(get(Unit::Hour, i64::MAX).is_err());
529
530        assert!(get(Unit::Day, 1).is_ok());
531        assert!(get(Unit::Day, 6).is_ok());
532        assert!(get(Unit::Day, 7).is_ok());
533        assert!(get(Unit::Day, 12).is_ok());
534        assert!(get(Unit::Day, 30).is_ok());
535        assert!(get(Unit::Day, 31).is_ok());
536        assert!(get(Unit::Day, 100).is_ok());
537        assert!(get(Unit::Day, 10_000_000).is_ok());
538        assert!(get(Unit::Day, 1_000_000_000).is_ok());
539        assert!(get(Unit::Day, -1).is_err());
540        assert!(get(Unit::Day, 0).is_err());
541        assert!(get(Unit::Day, 1_000_000_001).is_err());
542        assert!(get(Unit::Day, i64::MIN).is_err());
543        assert!(get(Unit::Day, i64::MAX).is_err());
544
545        assert!(get(Unit::Week, 1).is_ok());
546        assert!(get(Unit::Week, 6).is_ok());
547        assert!(get(Unit::Week, 7).is_ok());
548        assert!(get(Unit::Week, 12).is_ok());
549        assert!(get(Unit::Week, 30).is_ok());
550        assert!(get(Unit::Week, 31).is_ok());
551        assert!(get(Unit::Week, 100).is_ok());
552        assert!(get(Unit::Week, 2_000_000).is_ok());
553        assert!(get(Unit::Week, 1_000_000_000).is_ok());
554        assert!(get(Unit::Week, -1).is_err());
555        assert!(get(Unit::Week, 0).is_err());
556        assert!(get(Unit::Week, 1_000_000_001).is_err());
557        assert!(get(Unit::Week, i64::MIN).is_err());
558        assert!(get(Unit::Week, i64::MAX).is_err());
559
560        assert!(get(Unit::Month, 1).is_ok());
561        assert!(get(Unit::Month, 6).is_ok());
562        assert!(get(Unit::Month, 7).is_ok());
563        assert!(get(Unit::Month, 12).is_ok());
564        assert!(get(Unit::Month, 30).is_ok());
565        assert!(get(Unit::Month, 31).is_ok());
566        assert!(get(Unit::Month, 100).is_ok());
567        assert!(get(Unit::Month, 300_000).is_ok());
568        assert!(get(Unit::Month, 1_000_000_000).is_ok());
569        assert!(get(Unit::Month, -1).is_err());
570        assert!(get(Unit::Month, 0).is_err());
571        assert!(get(Unit::Month, 1_000_000_001).is_err());
572        assert!(get(Unit::Month, i64::MIN).is_err());
573        assert!(get(Unit::Month, i64::MAX).is_err());
574
575        assert!(get(Unit::Year, 1).is_ok());
576        assert!(get(Unit::Year, 6).is_ok());
577        assert!(get(Unit::Year, 7).is_ok());
578        assert!(get(Unit::Year, 12).is_ok());
579        assert!(get(Unit::Year, 30).is_ok());
580        assert!(get(Unit::Year, 31).is_ok());
581        assert!(get(Unit::Year, 100).is_ok());
582        assert!(get(Unit::Year, 50_000).is_ok());
583        assert!(get(Unit::Year, 1_000_000_000).is_ok());
584        assert!(get(Unit::Year, -1).is_err());
585        assert!(get(Unit::Year, 0).is_err());
586        assert!(get(Unit::Year, 1_000_000_001).is_err());
587        assert!(get(Unit::Year, i64::MIN).is_err());
588        assert!(get(Unit::Year, i64::MAX).is_err());
589    }
590
591    #[test]
592    fn for_datetime() {
593        let get = |unit, value| Increment::for_datetime(unit, value);
594
595        assert!(get(Unit::Nanosecond, 1).is_ok());
596        assert!(get(Unit::Nanosecond, 500).is_ok());
597        assert!(get(Unit::Nanosecond, -1).is_err());
598        assert!(get(Unit::Nanosecond, 0).is_err());
599        assert!(get(Unit::Nanosecond, 7).is_err());
600        assert!(get(Unit::Nanosecond, 1_000).is_err());
601        assert!(get(Unit::Nanosecond, i64::MIN).is_err());
602        assert!(get(Unit::Nanosecond, i64::MAX).is_err());
603
604        assert!(get(Unit::Microsecond, 1).is_ok());
605        assert!(get(Unit::Microsecond, 500).is_ok());
606        assert!(get(Unit::Microsecond, -1).is_err());
607        assert!(get(Unit::Microsecond, 0).is_err());
608        assert!(get(Unit::Microsecond, 7).is_err());
609        assert!(get(Unit::Microsecond, 1_000).is_err());
610        assert!(get(Unit::Microsecond, i64::MIN).is_err());
611        assert!(get(Unit::Microsecond, i64::MAX).is_err());
612
613        assert!(get(Unit::Millisecond, 1).is_ok());
614        assert!(get(Unit::Millisecond, 500).is_ok());
615        assert!(get(Unit::Millisecond, -1).is_err());
616        assert!(get(Unit::Millisecond, 0).is_err());
617        assert!(get(Unit::Millisecond, 7).is_err());
618        assert!(get(Unit::Millisecond, 1_000).is_err());
619        assert!(get(Unit::Millisecond, i64::MIN).is_err());
620        assert!(get(Unit::Millisecond, i64::MAX).is_err());
621
622        assert!(get(Unit::Second, 1).is_ok());
623        assert!(get(Unit::Second, 15).is_ok());
624        assert!(get(Unit::Second, -1).is_err());
625        assert!(get(Unit::Second, 0).is_err());
626        assert!(get(Unit::Second, 7).is_err());
627        assert!(get(Unit::Second, 1_000).is_err());
628        assert!(get(Unit::Second, i64::MIN).is_err());
629        assert!(get(Unit::Second, i64::MAX).is_err());
630
631        assert!(get(Unit::Minute, 1).is_ok());
632        assert!(get(Unit::Minute, 15).is_ok());
633        assert!(get(Unit::Minute, -1).is_err());
634        assert!(get(Unit::Minute, 0).is_err());
635        assert!(get(Unit::Minute, 7).is_err());
636        assert!(get(Unit::Minute, 1_000).is_err());
637        assert!(get(Unit::Minute, i64::MIN).is_err());
638        assert!(get(Unit::Minute, i64::MAX).is_err());
639
640        assert!(get(Unit::Hour, 1).is_ok());
641        assert!(get(Unit::Hour, 6).is_ok());
642        assert!(get(Unit::Hour, 12).is_ok());
643        assert!(get(Unit::Hour, -1).is_err());
644        assert!(get(Unit::Hour, 0).is_err());
645        assert!(get(Unit::Hour, 15).is_err());
646        assert!(get(Unit::Hour, 18).is_err());
647        assert!(get(Unit::Hour, 23).is_err());
648        assert!(get(Unit::Hour, 24).is_err());
649        assert!(get(Unit::Hour, i64::MIN).is_err());
650        assert!(get(Unit::Hour, i64::MAX).is_err());
651
652        assert!(get(Unit::Day, 1).is_ok());
653        assert!(get(Unit::Day, -1).is_err());
654        assert!(get(Unit::Day, 0).is_err());
655        assert!(get(Unit::Day, 2).is_err());
656        assert!(get(Unit::Day, 4).is_err());
657        assert!(get(Unit::Day, 7).is_err());
658        assert!(get(Unit::Day, i64::MIN).is_err());
659        assert!(get(Unit::Day, i64::MAX).is_err());
660    }
661
662    #[test]
663    fn for_time() {
664        let get = |unit, value| Increment::for_time(unit, value);
665
666        assert!(get(Unit::Nanosecond, 1).is_ok());
667        assert!(get(Unit::Nanosecond, 500).is_ok());
668        assert!(get(Unit::Nanosecond, -1).is_err());
669        assert!(get(Unit::Nanosecond, 0).is_err());
670        assert!(get(Unit::Nanosecond, 7).is_err());
671        assert!(get(Unit::Nanosecond, 1_000).is_err());
672        assert!(get(Unit::Nanosecond, i64::MIN).is_err());
673        assert!(get(Unit::Nanosecond, i64::MAX).is_err());
674
675        assert!(get(Unit::Microsecond, 1).is_ok());
676        assert!(get(Unit::Microsecond, 500).is_ok());
677        assert!(get(Unit::Microsecond, -1).is_err());
678        assert!(get(Unit::Microsecond, 0).is_err());
679        assert!(get(Unit::Microsecond, 7).is_err());
680        assert!(get(Unit::Microsecond, 1_000).is_err());
681        assert!(get(Unit::Microsecond, i64::MIN).is_err());
682        assert!(get(Unit::Microsecond, i64::MAX).is_err());
683
684        assert!(get(Unit::Millisecond, 1).is_ok());
685        assert!(get(Unit::Millisecond, 500).is_ok());
686        assert!(get(Unit::Millisecond, -1).is_err());
687        assert!(get(Unit::Millisecond, 0).is_err());
688        assert!(get(Unit::Millisecond, 7).is_err());
689        assert!(get(Unit::Millisecond, 1_000).is_err());
690        assert!(get(Unit::Millisecond, i64::MIN).is_err());
691        assert!(get(Unit::Millisecond, i64::MAX).is_err());
692
693        assert!(get(Unit::Second, 1).is_ok());
694        assert!(get(Unit::Second, 15).is_ok());
695        assert!(get(Unit::Second, -1).is_err());
696        assert!(get(Unit::Second, 0).is_err());
697        assert!(get(Unit::Second, 7).is_err());
698        assert!(get(Unit::Second, 1_000).is_err());
699        assert!(get(Unit::Second, i64::MIN).is_err());
700        assert!(get(Unit::Second, i64::MAX).is_err());
701
702        assert!(get(Unit::Minute, 1).is_ok());
703        assert!(get(Unit::Minute, 15).is_ok());
704        assert!(get(Unit::Minute, -1).is_err());
705        assert!(get(Unit::Minute, 0).is_err());
706        assert!(get(Unit::Minute, 7).is_err());
707        assert!(get(Unit::Minute, 1_000).is_err());
708        assert!(get(Unit::Minute, i64::MIN).is_err());
709        assert!(get(Unit::Minute, i64::MAX).is_err());
710
711        assert!(get(Unit::Hour, 1).is_ok());
712        assert!(get(Unit::Hour, 6).is_ok());
713        assert!(get(Unit::Hour, 12).is_ok());
714        assert!(get(Unit::Hour, -1).is_err());
715        assert!(get(Unit::Hour, 0).is_err());
716        assert!(get(Unit::Hour, 15).is_err());
717        assert!(get(Unit::Hour, 18).is_err());
718        assert!(get(Unit::Hour, 23).is_err());
719        assert!(get(Unit::Hour, 24).is_err());
720        assert!(get(Unit::Hour, i64::MIN).is_err());
721        assert!(get(Unit::Hour, i64::MAX).is_err());
722
723        assert!(get(Unit::Day, 1).is_err());
724        assert!(get(Unit::Day, -1).is_err());
725        assert!(get(Unit::Day, 0).is_err());
726        assert!(get(Unit::Day, 2).is_err());
727        assert!(get(Unit::Day, 4).is_err());
728        assert!(get(Unit::Day, 7).is_err());
729        assert!(get(Unit::Day, i64::MIN).is_err());
730        assert!(get(Unit::Day, i64::MAX).is_err());
731    }
732
733    #[test]
734    fn for_timestamp() {
735        let get = |unit, value| Increment::for_timestamp(unit, value);
736
737        assert!(get(Unit::Nanosecond, 1).is_ok());
738        assert!(get(Unit::Nanosecond, 500).is_ok());
739        assert!(get(Unit::Nanosecond, 1_000).is_ok());
740        assert!(get(Unit::Nanosecond, 1_000_000_000).is_ok());
741        assert!(get(Unit::Nanosecond, -1).is_err());
742        assert!(get(Unit::Nanosecond, 0).is_err());
743        assert!(get(Unit::Nanosecond, 7).is_err());
744        assert!(get(Unit::Nanosecond, 1_000_000_001).is_err());
745        assert!(get(Unit::Nanosecond, i64::MIN).is_err());
746        assert!(get(Unit::Nanosecond, i64::MAX).is_err());
747
748        assert!(get(Unit::Microsecond, 1).is_ok());
749        assert!(get(Unit::Microsecond, 500).is_ok());
750        assert!(get(Unit::Microsecond, 1_000).is_ok());
751        assert!(get(Unit::Microsecond, 2_000).is_ok());
752        assert!(get(Unit::Microsecond, -1).is_err());
753        assert!(get(Unit::Microsecond, 0).is_err());
754        assert!(get(Unit::Microsecond, 7).is_err());
755        assert!(get(Unit::Microsecond, 1_000_000_000).is_err());
756        assert!(get(Unit::Microsecond, 1_000_000_001).is_err());
757        assert!(get(Unit::Microsecond, i64::MIN).is_err());
758        assert!(get(Unit::Microsecond, i64::MAX).is_err());
759
760        assert!(get(Unit::Millisecond, 1).is_ok());
761        assert!(get(Unit::Millisecond, 500).is_ok());
762        assert!(get(Unit::Millisecond, 1_000).is_ok());
763        assert!(get(Unit::Millisecond, 2_000).is_ok());
764        assert!(get(Unit::Millisecond, 86_400_000).is_ok());
765        assert!(get(Unit::Millisecond, -1).is_err());
766        assert!(get(Unit::Millisecond, 0).is_err());
767        assert!(get(Unit::Millisecond, 7).is_err());
768        assert!(get(Unit::Millisecond, 1_000_000_000).is_err());
769        assert!(get(Unit::Millisecond, 1_000_000_001).is_err());
770        assert!(get(Unit::Millisecond, i64::MIN).is_err());
771        assert!(get(Unit::Millisecond, i64::MAX).is_err());
772
773        assert!(get(Unit::Second, 1).is_ok());
774        assert!(get(Unit::Second, 15).is_ok());
775        assert!(get(Unit::Second, 3_600).is_ok());
776        assert!(get(Unit::Second, 86_400).is_ok());
777        assert!(get(Unit::Second, -1).is_err());
778        assert!(get(Unit::Second, 0).is_err());
779        assert!(get(Unit::Second, 7).is_err());
780        assert!(get(Unit::Second, 1_000).is_err());
781        assert!(get(Unit::Second, 86_401).is_err());
782        assert!(get(Unit::Second, 172_800).is_err());
783        assert!(get(Unit::Second, 1_000_000_000).is_err());
784        assert!(get(Unit::Second, 1_000_000_001).is_err());
785        assert!(get(Unit::Second, i64::MIN).is_err());
786        assert!(get(Unit::Second, i64::MAX).is_err());
787
788        assert!(get(Unit::Minute, 1).is_ok());
789        assert!(get(Unit::Minute, 15).is_ok());
790        assert!(get(Unit::Minute, 1_440).is_ok());
791        assert!(get(Unit::Minute, -1).is_err());
792        assert!(get(Unit::Minute, 0).is_err());
793        assert!(get(Unit::Minute, 7).is_err());
794        assert!(get(Unit::Minute, 1_000).is_err());
795        assert!(get(Unit::Minute, 1_441).is_err());
796        assert!(get(Unit::Minute, 2_880).is_err());
797        assert!(get(Unit::Minute, 1_000_000_000).is_err());
798        assert!(get(Unit::Minute, 1_000_000_001).is_err());
799        assert!(get(Unit::Minute, i64::MIN).is_err());
800        assert!(get(Unit::Minute, i64::MAX).is_err());
801
802        assert!(get(Unit::Hour, 1).is_ok());
803        assert!(get(Unit::Hour, 6).is_ok());
804        assert!(get(Unit::Hour, 12).is_ok());
805        assert!(get(Unit::Hour, 24).is_ok());
806        assert!(get(Unit::Hour, -1).is_err());
807        assert!(get(Unit::Hour, 0).is_err());
808        assert!(get(Unit::Hour, 15).is_err());
809        assert!(get(Unit::Hour, 18).is_err());
810        assert!(get(Unit::Hour, 23).is_err());
811        assert!(get(Unit::Hour, 25).is_err());
812        assert!(get(Unit::Hour, 1_000_000_000).is_err());
813        assert!(get(Unit::Hour, 1_000_000_001).is_err());
814        assert!(get(Unit::Hour, i64::MIN).is_err());
815        assert!(get(Unit::Hour, i64::MAX).is_err());
816
817        assert!(get(Unit::Day, 1).is_err());
818        assert!(get(Unit::Day, -1).is_err());
819        assert!(get(Unit::Day, 0).is_err());
820        assert!(get(Unit::Day, 2).is_err());
821        assert!(get(Unit::Day, 4).is_err());
822        assert!(get(Unit::Day, 7).is_err());
823        assert!(get(Unit::Day, i64::MIN).is_err());
824        assert!(get(Unit::Day, i64::MAX).is_err());
825
826        assert!(get(Unit::Week, 1).is_err());
827        assert!(get(Unit::Week, -1).is_err());
828        assert!(get(Unit::Week, 0).is_err());
829        assert!(get(Unit::Week, 2).is_err());
830        assert!(get(Unit::Week, 4).is_err());
831        assert!(get(Unit::Week, 7).is_err());
832        assert!(get(Unit::Week, i64::MIN).is_err());
833        assert!(get(Unit::Week, i64::MAX).is_err());
834
835        assert!(get(Unit::Month, 1).is_err());
836        assert!(get(Unit::Month, -1).is_err());
837        assert!(get(Unit::Month, 0).is_err());
838        assert!(get(Unit::Month, 2).is_err());
839        assert!(get(Unit::Month, 4).is_err());
840        assert!(get(Unit::Month, 7).is_err());
841        assert!(get(Unit::Month, i64::MIN).is_err());
842        assert!(get(Unit::Month, i64::MAX).is_err());
843
844        assert!(get(Unit::Year, 1).is_err());
845        assert!(get(Unit::Year, -1).is_err());
846        assert!(get(Unit::Year, 0).is_err());
847        assert!(get(Unit::Year, 2).is_err());
848        assert!(get(Unit::Year, 4).is_err());
849        assert!(get(Unit::Year, 7).is_err());
850        assert!(get(Unit::Year, i64::MIN).is_err());
851        assert!(get(Unit::Year, i64::MAX).is_err());
852    }
853
854    // Some ad hoc tests I wrote while writing the rounding increment code.
855    #[test]
856    fn round_to_increment_half_expand_ad_hoc() {
857        let round = |quantity: i128, increment: i128| -> i128 {
858            RoundMode::HalfExpand.round_by_i128(quantity, increment)
859        };
860        assert_eq!(26, round(20, 13));
861
862        assert_eq!(0, round(29, 60));
863        assert_eq!(60, round(30, 60));
864        assert_eq!(60, round(31, 60));
865
866        assert_eq!(0, round(3, 7));
867        assert_eq!(7, round(4, 7));
868    }
869
870    // The Temporal tests are inspired by the table from here:
871    // https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement
872    //
873    // The main difference is that our rounding function specifically does not
874    // use floating point, so we tweak the values a bit.
875
876    #[test]
877    fn round_to_increment_temporal_table_ceil() {
878        let round = |quantity: i128, increment: i128| -> i128 {
879            RoundMode::Ceil.round_by_i128(quantity, increment)
880        };
881        assert_eq!(-10, round(-15, 10));
882        assert_eq!(0, round(-5, 10));
883        assert_eq!(10, round(4, 10));
884        assert_eq!(10, round(5, 10));
885        assert_eq!(10, round(6, 10));
886        assert_eq!(20, round(15, 10));
887
888        assert_eq!(i128::MAX, round(i128::MAX, 1));
889        assert_eq!(i128::MAX, round(i128::MAX, i128::MAX));
890
891        // TODO: This test currently panics on overflow.
892        // We should overall scrutinize the input constraints
893        // for our rounding routines and align with their call
894        // sites. We were previously relying on ranged integers
895        // to holistically verify we weren't doing anything that
896        // could result in overflow. I suspect that we can't
897        // actually support the i128 quantity range (and I don't
898        // think we need to). The question is whether we should
899        // make the rounding interface fallible or if this
900        // should just document panicking conditions.
901        //
902        // Oh, we should introduce a new internal `Increment`
903        // wrapper that provides guarantees about its range.
904        // assert_eq!(i128::MIN, round(i128::MIN, -1));
905        // assert_eq!(i128::MIN, round(i128::MIN, i128::MIN));
906    }
907
908    #[test]
909    fn round_to_increment_temporal_table_floor() {
910        let round = |quantity: i128, increment: i128| -> i128 {
911            RoundMode::Floor.round_by_i128(quantity, increment)
912        };
913        assert_eq!(-20, round(-15, 10));
914        assert_eq!(-10, round(-5, 10));
915        assert_eq!(0, round(4, 10));
916        assert_eq!(0, round(5, 10));
917        assert_eq!(0, round(6, 10));
918        assert_eq!(10, round(15, 10));
919    }
920
921    #[test]
922    fn round_to_increment_temporal_table_expand() {
923        let round = |quantity: i128, increment: i128| -> i128 {
924            RoundMode::Expand.round_by_i128(quantity, increment)
925        };
926        assert_eq!(-20, round(-15, 10));
927        assert_eq!(-10, round(-5, 10));
928        assert_eq!(10, round(4, 10));
929        assert_eq!(10, round(5, 10));
930        assert_eq!(10, round(6, 10));
931        assert_eq!(20, round(15, 10));
932    }
933
934    #[test]
935    fn round_to_increment_temporal_table_trunc() {
936        let round = |quantity: i128, increment: i128| -> i128 {
937            RoundMode::Trunc.round_by_i128(quantity, increment)
938        };
939        assert_eq!(-10, round(-15, 10));
940        assert_eq!(0, round(-5, 10));
941        assert_eq!(0, round(4, 10));
942        assert_eq!(0, round(5, 10));
943        assert_eq!(0, round(6, 10));
944        assert_eq!(10, round(15, 10));
945    }
946
947    #[test]
948    fn round_to_increment_temporal_table_half_ceil() {
949        let round = |quantity: i128, increment: i128| -> i128 {
950            RoundMode::HalfCeil.round_by_i128(quantity, increment)
951        };
952        assert_eq!(-10, round(-15, 10));
953        assert_eq!(0, round(-5, 10));
954        assert_eq!(0, round(4, 10));
955        assert_eq!(10, round(5, 10));
956        assert_eq!(10, round(6, 10));
957        assert_eq!(20, round(15, 10));
958    }
959
960    #[test]
961    fn round_to_increment_temporal_table_half_floor() {
962        let round = |quantity: i128, increment: i128| -> i128 {
963            RoundMode::HalfFloor.round_by_i128(quantity, increment)
964        };
965        assert_eq!(-20, round(-15, 10));
966        assert_eq!(-10, round(-5, 10));
967        assert_eq!(0, round(4, 10));
968        assert_eq!(0, round(5, 10));
969        assert_eq!(10, round(6, 10));
970        assert_eq!(10, round(15, 10));
971    }
972
973    #[test]
974    fn round_to_increment_temporal_table_half_expand() {
975        let round = |quantity: i128, increment: i128| -> i128 {
976            RoundMode::HalfExpand.round_by_i128(quantity, increment)
977        };
978        assert_eq!(-20, round(-15, 10));
979        assert_eq!(-10, round(-5, 10));
980        assert_eq!(0, round(4, 10));
981        assert_eq!(10, round(5, 10));
982        assert_eq!(10, round(6, 10));
983        assert_eq!(20, round(15, 10));
984    }
985
986    #[test]
987    fn round_to_increment_temporal_table_half_trunc() {
988        let round = |quantity: i128, increment: i128| -> i128 {
989            RoundMode::HalfTrunc.round_by_i128(quantity, increment)
990        };
991        assert_eq!(-10, round(-15, 10));
992        assert_eq!(0, round(-5, 10));
993        assert_eq!(0, round(4, 10));
994        assert_eq!(0, round(5, 10));
995        assert_eq!(10, round(6, 10));
996        assert_eq!(10, round(15, 10));
997    }
998
999    #[test]
1000    fn round_to_increment_temporal_table_half_even() {
1001        let round = |quantity: i128, increment: i128| -> i128 {
1002            RoundMode::HalfEven.round_by_i128(quantity, increment)
1003        };
1004        assert_eq!(-20, round(-15, 10));
1005        assert_eq!(0, round(-5, 10));
1006        assert_eq!(0, round(4, 10));
1007        assert_eq!(0, round(5, 10));
1008        assert_eq!(10, round(6, 10));
1009        assert_eq!(20, round(15, 10));
1010    }
1011
1012    // Tests that the maximum possible increment, in units of nanoseconds,
1013    // fits into a `SignedDuration`.
1014    #[test]
1015    fn maximum_increment_nanos() {
1016        assert!(Increment::for_span(Unit::Week, 1_000_000_000).is_ok());
1017        assert!(Increment::for_span(Unit::Week, 1_000_000_001).is_err());
1018        assert_eq!(
1019            Unit::Week.duration() * 1_000_000_000,
1020            SignedDuration::from_secs(168_000_000_000 * 60 * 60)
1021        );
1022    }
1023
1024    // Tests some edge cases where rounding can actually overflow. We ensure
1025    // that an error is returned instead of panicking.
1026    //
1027    // I do not believe it's possible for these overflows to be observed using
1028    // Jiff's public API however.
1029    #[test]
1030    fn round_by_duration_overflow() {
1031        let mode = RoundMode::Expand;
1032        let quantity = SignedDuration::MAX;
1033        let increment = SignedDuration::MAX - SignedDuration::from_secs(1);
1034        assert!(mode.round_by_duration(quantity, increment).is_err());
1035
1036        let mode = RoundMode::Expand;
1037        let quantity = SignedDuration::MIN;
1038        let increment = SignedDuration::MAX;
1039        assert!(mode.round_by_duration(quantity, increment).is_err());
1040    }
1041}