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)]
294pub enum RoundMode {
295    /// Rounds toward positive infinity.
296    ///
297    /// For negative spans and datetimes, this option will make the value
298    /// smaller, which could be unexpected. To round away from zero, use
299    /// `Expand`.
300    Ceil,
301    /// Rounds toward negative infinity.
302    ///
303    /// This mode acts like `Trunc` for positive spans and datetimes, but
304    /// for negative values it will make the value larger, which could be
305    /// unexpected. To round towards zero, use `Trunc`.
306    Floor,
307    /// Rounds away from zero like `Ceil` for positive spans and datetimes,
308    /// and like `Floor` for negative spans and datetimes.
309    Expand,
310    /// Rounds toward zero, chopping off any fractional part of a unit.
311    ///
312    /// This is the default when rounding spans returned from
313    /// datetime arithmetic. (But it is not the default for
314    /// [`Span::round`](crate::Span::round).)
315    Trunc,
316    /// Rounds to the nearest allowed value like `HalfExpand`, but when there
317    /// is a tie, round towards positive infinity like `Ceil`.
318    HalfCeil,
319    /// Rounds to the nearest allowed value like `HalfExpand`, but when there
320    /// is a tie, round towards negative infinity like `Floor`.
321    HalfFloor,
322    /// Rounds to the nearest value allowed by the rounding increment and the
323    /// smallest unit. When there is a tie, round away from zero like `Ceil`
324    /// for positive spans and datetimes and like `Floor` for negative spans
325    /// and datetimes.
326    ///
327    /// This corresponds to how rounding is often taught in school.
328    ///
329    /// This is the default for rounding spans and datetimes.
330    HalfExpand,
331    /// Rounds to the nearest allowed value like `HalfExpand`, but when there
332    /// is a tie, round towards zero like `Trunc`.
333    HalfTrunc,
334    /// Rounds to the nearest allowed value like `HalfExpand`, but when there
335    /// is a tie, round towards the value that is an even multiple of the
336    /// rounding increment. For example, with a rounding increment of `3`,
337    /// the number `10` would round up to `12` instead of down to `9`, because
338    /// `12` is an even multiple of `3`, where as `9` is is an odd multiple.
339    HalfEven,
340}
341
342impl RoundMode {
343    /// Round `quantity` to the nearest `increment` using this rounding mode.
344    ///
345    /// If this the rounding result would overflow `SignedDuration`, then an
346    /// error is returned.
347    ///
348    /// Callers should generally prefer higher level APIs. But this one is
349    /// unavoidable when the increment isn't tied to an invariant length and
350    /// can vary.
351    ///
352    /// # Panics
353    ///
354    /// Callers must ensure that `increment` has a number of nanoseconds
355    /// greater than or equal to `1`.
356    pub(crate) fn round_by_duration(
357        self,
358        quantity: SignedDuration,
359        increment: SignedDuration,
360    ) -> Result<SignedDuration, Error> {
361        let quantity = quantity.as_nanos();
362        let increment = increment.as_nanos();
363        assert!(increment >= 1);
364        let rounded = self.round_by_i128(quantity, increment);
365        // This can fail in the case where `quantity` is somehow rounded to a
366        // value above/below a signed duration's max/min. This cannot *usually*
367        // happen in practice because our `quantity` is typically limited by
368        // the maximum Jiff datetime span. Even spanning from -9999 to 9999, a
369        // 96-bit integer number of nanoseconds still can't cause an overflow.
370        // Moreover, `increment` is usually capped to `1_000_000_000`. The only
371        // case where it isn't is when we're rounding based on variable length
372        // units (in which case, this lower level rounding routine is called
373        // directly). For example, when rounding to years with an increment
374        // (set by the user) to `15_000`. That will get translated to 15,000
375        // years *in nanoseconds*.
376        //
377        // Even still, the complete span of time supported by Jiff can
378        // comfortably fit into a `SignedDuration`. And because of the limit
379        // placed on `increment`, I don't believe this error can ever actually
380        // occur.
381        SignedDuration::try_from_nanos_i128(rounded)
382            .ok_or_else(|| Error::from(b::SignedDurationSeconds::error()))
383    }
384
385    /// The internal API that does the actual rounding.
386    ///
387    /// This does not error on overflow because callers can never use values
388    /// close to the `i128` limits. If an overflow would otherwise occur due to
389    /// rounding, then it saturates instead of panicking or returning an error.
390    ///
391    /// I wish we could do modulus on `SignedDuration` directly. Then we
392    /// wouldn't need 128-bit arithmetic at all. But I looked into making
393    /// `SignedDuration % SignedDuration` work, and it was quite involved? So I
394    /// guess we should just let the compiler handle it for `i128`. We'd also
395    /// need division.
396    ///
397    /// # Panics
398    ///
399    /// When `increment < 1`.
400    fn round_by_i128(self, quantity: i128, increment: i128) -> i128 {
401        assert!(increment >= 1);
402        // ref: https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement
403        let mode = self;
404        let mut quotient = quantity / increment;
405        let remainder = quantity % increment;
406        if remainder == 0 {
407            return quantity;
408        }
409        let sign = if remainder < 0 { -1 } else { 1 };
410        let tiebreaker = (remainder * 2).abs();
411        let tie = tiebreaker == increment;
412        let expand_is_nearer = tiebreaker > increment;
413        // ref: https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement
414        match mode {
415            RoundMode::Ceil => {
416                if sign > 0 {
417                    quotient += sign;
418                }
419            }
420            RoundMode::Floor => {
421                if sign < 0 {
422                    quotient += sign;
423                }
424            }
425            RoundMode::Expand => {
426                quotient += sign;
427            }
428            RoundMode::Trunc => {}
429            RoundMode::HalfCeil => {
430                if expand_is_nearer || (tie && sign > 0) {
431                    quotient += sign;
432                }
433            }
434            RoundMode::HalfFloor => {
435                if expand_is_nearer || (tie && sign < 0) {
436                    quotient += sign;
437                }
438            }
439            RoundMode::HalfExpand => {
440                if expand_is_nearer || tie {
441                    quotient += sign;
442                }
443            }
444            RoundMode::HalfTrunc => {
445                if expand_is_nearer {
446                    quotient += sign;
447                }
448            }
449            RoundMode::HalfEven => {
450                if expand_is_nearer || (tie && quotient.rem_euclid(2) == 1) {
451                    quotient += sign;
452                }
453            }
454        }
455        // We use saturating arithmetic here because this can overflow when
456        // `quantity` is the max value. Since we're rounding, we just refuse to
457        // go over the maximum. This can't happen in practice because this is
458        // a private API and all paths to this API enforce that we are never
459        // going to round over the `i128` maximum.
460        quotient.saturating_mul(increment)
461    }
462}
463
464#[cfg(test)]
465mod tests {
466    use super::*;
467
468    #[test]
469    fn for_span() {
470        let get = |unit, value| Increment::for_span(unit, value);
471
472        assert!(get(Unit::Nanosecond, 1).is_ok());
473        assert!(get(Unit::Nanosecond, 500).is_ok());
474        assert!(get(Unit::Nanosecond, -1).is_err());
475        assert!(get(Unit::Nanosecond, 0).is_err());
476        assert!(get(Unit::Nanosecond, 7).is_err());
477        assert!(get(Unit::Nanosecond, 1_000).is_err());
478        assert!(get(Unit::Nanosecond, i64::MIN).is_err());
479        assert!(get(Unit::Nanosecond, i64::MAX).is_err());
480
481        assert!(get(Unit::Microsecond, 1).is_ok());
482        assert!(get(Unit::Microsecond, 500).is_ok());
483        assert!(get(Unit::Microsecond, -1).is_err());
484        assert!(get(Unit::Microsecond, 0).is_err());
485        assert!(get(Unit::Microsecond, 7).is_err());
486        assert!(get(Unit::Microsecond, 1_000).is_err());
487        assert!(get(Unit::Microsecond, i64::MIN).is_err());
488        assert!(get(Unit::Microsecond, i64::MAX).is_err());
489
490        assert!(get(Unit::Millisecond, 1).is_ok());
491        assert!(get(Unit::Millisecond, 500).is_ok());
492        assert!(get(Unit::Millisecond, -1).is_err());
493        assert!(get(Unit::Millisecond, 0).is_err());
494        assert!(get(Unit::Millisecond, 7).is_err());
495        assert!(get(Unit::Millisecond, 1_000).is_err());
496        assert!(get(Unit::Millisecond, i64::MIN).is_err());
497        assert!(get(Unit::Millisecond, i64::MAX).is_err());
498
499        assert!(get(Unit::Second, 1).is_ok());
500        assert!(get(Unit::Second, 15).is_ok());
501        assert!(get(Unit::Second, -1).is_err());
502        assert!(get(Unit::Second, 0).is_err());
503        assert!(get(Unit::Second, 7).is_err());
504        assert!(get(Unit::Second, 1_000).is_err());
505        assert!(get(Unit::Second, i64::MIN).is_err());
506        assert!(get(Unit::Second, i64::MAX).is_err());
507
508        assert!(get(Unit::Minute, 1).is_ok());
509        assert!(get(Unit::Minute, 15).is_ok());
510        assert!(get(Unit::Minute, -1).is_err());
511        assert!(get(Unit::Minute, 0).is_err());
512        assert!(get(Unit::Minute, 7).is_err());
513        assert!(get(Unit::Minute, 1_000).is_err());
514        assert!(get(Unit::Minute, i64::MIN).is_err());
515        assert!(get(Unit::Minute, i64::MAX).is_err());
516
517        assert!(get(Unit::Hour, 1).is_ok());
518        assert!(get(Unit::Hour, 6).is_ok());
519        assert!(get(Unit::Hour, 12).is_ok());
520        assert!(get(Unit::Hour, -1).is_err());
521        assert!(get(Unit::Hour, 0).is_err());
522        assert!(get(Unit::Hour, 15).is_err());
523        assert!(get(Unit::Hour, 18).is_err());
524        assert!(get(Unit::Hour, 23).is_err());
525        assert!(get(Unit::Hour, 24).is_err());
526        assert!(get(Unit::Hour, i64::MIN).is_err());
527        assert!(get(Unit::Hour, i64::MAX).is_err());
528
529        assert!(get(Unit::Day, 1).is_ok());
530        assert!(get(Unit::Day, 6).is_ok());
531        assert!(get(Unit::Day, 7).is_ok());
532        assert!(get(Unit::Day, 12).is_ok());
533        assert!(get(Unit::Day, 30).is_ok());
534        assert!(get(Unit::Day, 31).is_ok());
535        assert!(get(Unit::Day, 100).is_ok());
536        assert!(get(Unit::Day, 10_000_000).is_ok());
537        assert!(get(Unit::Day, 1_000_000_000).is_ok());
538        assert!(get(Unit::Day, -1).is_err());
539        assert!(get(Unit::Day, 0).is_err());
540        assert!(get(Unit::Day, 1_000_000_001).is_err());
541        assert!(get(Unit::Day, i64::MIN).is_err());
542        assert!(get(Unit::Day, i64::MAX).is_err());
543
544        assert!(get(Unit::Week, 1).is_ok());
545        assert!(get(Unit::Week, 6).is_ok());
546        assert!(get(Unit::Week, 7).is_ok());
547        assert!(get(Unit::Week, 12).is_ok());
548        assert!(get(Unit::Week, 30).is_ok());
549        assert!(get(Unit::Week, 31).is_ok());
550        assert!(get(Unit::Week, 100).is_ok());
551        assert!(get(Unit::Week, 2_000_000).is_ok());
552        assert!(get(Unit::Week, 1_000_000_000).is_ok());
553        assert!(get(Unit::Week, -1).is_err());
554        assert!(get(Unit::Week, 0).is_err());
555        assert!(get(Unit::Week, 1_000_000_001).is_err());
556        assert!(get(Unit::Week, i64::MIN).is_err());
557        assert!(get(Unit::Week, i64::MAX).is_err());
558
559        assert!(get(Unit::Month, 1).is_ok());
560        assert!(get(Unit::Month, 6).is_ok());
561        assert!(get(Unit::Month, 7).is_ok());
562        assert!(get(Unit::Month, 12).is_ok());
563        assert!(get(Unit::Month, 30).is_ok());
564        assert!(get(Unit::Month, 31).is_ok());
565        assert!(get(Unit::Month, 100).is_ok());
566        assert!(get(Unit::Month, 300_000).is_ok());
567        assert!(get(Unit::Month, 1_000_000_000).is_ok());
568        assert!(get(Unit::Month, -1).is_err());
569        assert!(get(Unit::Month, 0).is_err());
570        assert!(get(Unit::Month, 1_000_000_001).is_err());
571        assert!(get(Unit::Month, i64::MIN).is_err());
572        assert!(get(Unit::Month, i64::MAX).is_err());
573
574        assert!(get(Unit::Year, 1).is_ok());
575        assert!(get(Unit::Year, 6).is_ok());
576        assert!(get(Unit::Year, 7).is_ok());
577        assert!(get(Unit::Year, 12).is_ok());
578        assert!(get(Unit::Year, 30).is_ok());
579        assert!(get(Unit::Year, 31).is_ok());
580        assert!(get(Unit::Year, 100).is_ok());
581        assert!(get(Unit::Year, 50_000).is_ok());
582        assert!(get(Unit::Year, 1_000_000_000).is_ok());
583        assert!(get(Unit::Year, -1).is_err());
584        assert!(get(Unit::Year, 0).is_err());
585        assert!(get(Unit::Year, 1_000_000_001).is_err());
586        assert!(get(Unit::Year, i64::MIN).is_err());
587        assert!(get(Unit::Year, i64::MAX).is_err());
588    }
589
590    #[test]
591    fn for_datetime() {
592        let get = |unit, value| Increment::for_datetime(unit, value);
593
594        assert!(get(Unit::Nanosecond, 1).is_ok());
595        assert!(get(Unit::Nanosecond, 500).is_ok());
596        assert!(get(Unit::Nanosecond, -1).is_err());
597        assert!(get(Unit::Nanosecond, 0).is_err());
598        assert!(get(Unit::Nanosecond, 7).is_err());
599        assert!(get(Unit::Nanosecond, 1_000).is_err());
600        assert!(get(Unit::Nanosecond, i64::MIN).is_err());
601        assert!(get(Unit::Nanosecond, i64::MAX).is_err());
602
603        assert!(get(Unit::Microsecond, 1).is_ok());
604        assert!(get(Unit::Microsecond, 500).is_ok());
605        assert!(get(Unit::Microsecond, -1).is_err());
606        assert!(get(Unit::Microsecond, 0).is_err());
607        assert!(get(Unit::Microsecond, 7).is_err());
608        assert!(get(Unit::Microsecond, 1_000).is_err());
609        assert!(get(Unit::Microsecond, i64::MIN).is_err());
610        assert!(get(Unit::Microsecond, i64::MAX).is_err());
611
612        assert!(get(Unit::Millisecond, 1).is_ok());
613        assert!(get(Unit::Millisecond, 500).is_ok());
614        assert!(get(Unit::Millisecond, -1).is_err());
615        assert!(get(Unit::Millisecond, 0).is_err());
616        assert!(get(Unit::Millisecond, 7).is_err());
617        assert!(get(Unit::Millisecond, 1_000).is_err());
618        assert!(get(Unit::Millisecond, i64::MIN).is_err());
619        assert!(get(Unit::Millisecond, i64::MAX).is_err());
620
621        assert!(get(Unit::Second, 1).is_ok());
622        assert!(get(Unit::Second, 15).is_ok());
623        assert!(get(Unit::Second, -1).is_err());
624        assert!(get(Unit::Second, 0).is_err());
625        assert!(get(Unit::Second, 7).is_err());
626        assert!(get(Unit::Second, 1_000).is_err());
627        assert!(get(Unit::Second, i64::MIN).is_err());
628        assert!(get(Unit::Second, i64::MAX).is_err());
629
630        assert!(get(Unit::Minute, 1).is_ok());
631        assert!(get(Unit::Minute, 15).is_ok());
632        assert!(get(Unit::Minute, -1).is_err());
633        assert!(get(Unit::Minute, 0).is_err());
634        assert!(get(Unit::Minute, 7).is_err());
635        assert!(get(Unit::Minute, 1_000).is_err());
636        assert!(get(Unit::Minute, i64::MIN).is_err());
637        assert!(get(Unit::Minute, i64::MAX).is_err());
638
639        assert!(get(Unit::Hour, 1).is_ok());
640        assert!(get(Unit::Hour, 6).is_ok());
641        assert!(get(Unit::Hour, 12).is_ok());
642        assert!(get(Unit::Hour, -1).is_err());
643        assert!(get(Unit::Hour, 0).is_err());
644        assert!(get(Unit::Hour, 15).is_err());
645        assert!(get(Unit::Hour, 18).is_err());
646        assert!(get(Unit::Hour, 23).is_err());
647        assert!(get(Unit::Hour, 24).is_err());
648        assert!(get(Unit::Hour, i64::MIN).is_err());
649        assert!(get(Unit::Hour, i64::MAX).is_err());
650
651        assert!(get(Unit::Day, 1).is_ok());
652        assert!(get(Unit::Day, -1).is_err());
653        assert!(get(Unit::Day, 0).is_err());
654        assert!(get(Unit::Day, 2).is_err());
655        assert!(get(Unit::Day, 4).is_err());
656        assert!(get(Unit::Day, 7).is_err());
657        assert!(get(Unit::Day, i64::MIN).is_err());
658        assert!(get(Unit::Day, i64::MAX).is_err());
659    }
660
661    #[test]
662    fn for_time() {
663        let get = |unit, value| Increment::for_time(unit, value);
664
665        assert!(get(Unit::Nanosecond, 1).is_ok());
666        assert!(get(Unit::Nanosecond, 500).is_ok());
667        assert!(get(Unit::Nanosecond, -1).is_err());
668        assert!(get(Unit::Nanosecond, 0).is_err());
669        assert!(get(Unit::Nanosecond, 7).is_err());
670        assert!(get(Unit::Nanosecond, 1_000).is_err());
671        assert!(get(Unit::Nanosecond, i64::MIN).is_err());
672        assert!(get(Unit::Nanosecond, i64::MAX).is_err());
673
674        assert!(get(Unit::Microsecond, 1).is_ok());
675        assert!(get(Unit::Microsecond, 500).is_ok());
676        assert!(get(Unit::Microsecond, -1).is_err());
677        assert!(get(Unit::Microsecond, 0).is_err());
678        assert!(get(Unit::Microsecond, 7).is_err());
679        assert!(get(Unit::Microsecond, 1_000).is_err());
680        assert!(get(Unit::Microsecond, i64::MIN).is_err());
681        assert!(get(Unit::Microsecond, i64::MAX).is_err());
682
683        assert!(get(Unit::Millisecond, 1).is_ok());
684        assert!(get(Unit::Millisecond, 500).is_ok());
685        assert!(get(Unit::Millisecond, -1).is_err());
686        assert!(get(Unit::Millisecond, 0).is_err());
687        assert!(get(Unit::Millisecond, 7).is_err());
688        assert!(get(Unit::Millisecond, 1_000).is_err());
689        assert!(get(Unit::Millisecond, i64::MIN).is_err());
690        assert!(get(Unit::Millisecond, i64::MAX).is_err());
691
692        assert!(get(Unit::Second, 1).is_ok());
693        assert!(get(Unit::Second, 15).is_ok());
694        assert!(get(Unit::Second, -1).is_err());
695        assert!(get(Unit::Second, 0).is_err());
696        assert!(get(Unit::Second, 7).is_err());
697        assert!(get(Unit::Second, 1_000).is_err());
698        assert!(get(Unit::Second, i64::MIN).is_err());
699        assert!(get(Unit::Second, i64::MAX).is_err());
700
701        assert!(get(Unit::Minute, 1).is_ok());
702        assert!(get(Unit::Minute, 15).is_ok());
703        assert!(get(Unit::Minute, -1).is_err());
704        assert!(get(Unit::Minute, 0).is_err());
705        assert!(get(Unit::Minute, 7).is_err());
706        assert!(get(Unit::Minute, 1_000).is_err());
707        assert!(get(Unit::Minute, i64::MIN).is_err());
708        assert!(get(Unit::Minute, i64::MAX).is_err());
709
710        assert!(get(Unit::Hour, 1).is_ok());
711        assert!(get(Unit::Hour, 6).is_ok());
712        assert!(get(Unit::Hour, 12).is_ok());
713        assert!(get(Unit::Hour, -1).is_err());
714        assert!(get(Unit::Hour, 0).is_err());
715        assert!(get(Unit::Hour, 15).is_err());
716        assert!(get(Unit::Hour, 18).is_err());
717        assert!(get(Unit::Hour, 23).is_err());
718        assert!(get(Unit::Hour, 24).is_err());
719        assert!(get(Unit::Hour, i64::MIN).is_err());
720        assert!(get(Unit::Hour, i64::MAX).is_err());
721
722        assert!(get(Unit::Day, 1).is_err());
723        assert!(get(Unit::Day, -1).is_err());
724        assert!(get(Unit::Day, 0).is_err());
725        assert!(get(Unit::Day, 2).is_err());
726        assert!(get(Unit::Day, 4).is_err());
727        assert!(get(Unit::Day, 7).is_err());
728        assert!(get(Unit::Day, i64::MIN).is_err());
729        assert!(get(Unit::Day, i64::MAX).is_err());
730    }
731
732    #[test]
733    fn for_timestamp() {
734        let get = |unit, value| Increment::for_timestamp(unit, value);
735
736        assert!(get(Unit::Nanosecond, 1).is_ok());
737        assert!(get(Unit::Nanosecond, 500).is_ok());
738        assert!(get(Unit::Nanosecond, 1_000).is_ok());
739        assert!(get(Unit::Nanosecond, 1_000_000_000).is_ok());
740        assert!(get(Unit::Nanosecond, -1).is_err());
741        assert!(get(Unit::Nanosecond, 0).is_err());
742        assert!(get(Unit::Nanosecond, 7).is_err());
743        assert!(get(Unit::Nanosecond, 1_000_000_001).is_err());
744        assert!(get(Unit::Nanosecond, i64::MIN).is_err());
745        assert!(get(Unit::Nanosecond, i64::MAX).is_err());
746
747        assert!(get(Unit::Microsecond, 1).is_ok());
748        assert!(get(Unit::Microsecond, 500).is_ok());
749        assert!(get(Unit::Microsecond, 1_000).is_ok());
750        assert!(get(Unit::Microsecond, 2_000).is_ok());
751        assert!(get(Unit::Microsecond, -1).is_err());
752        assert!(get(Unit::Microsecond, 0).is_err());
753        assert!(get(Unit::Microsecond, 7).is_err());
754        assert!(get(Unit::Microsecond, 1_000_000_000).is_err());
755        assert!(get(Unit::Microsecond, 1_000_000_001).is_err());
756        assert!(get(Unit::Microsecond, i64::MIN).is_err());
757        assert!(get(Unit::Microsecond, i64::MAX).is_err());
758
759        assert!(get(Unit::Millisecond, 1).is_ok());
760        assert!(get(Unit::Millisecond, 500).is_ok());
761        assert!(get(Unit::Millisecond, 1_000).is_ok());
762        assert!(get(Unit::Millisecond, 2_000).is_ok());
763        assert!(get(Unit::Millisecond, 86_400_000).is_ok());
764        assert!(get(Unit::Millisecond, -1).is_err());
765        assert!(get(Unit::Millisecond, 0).is_err());
766        assert!(get(Unit::Millisecond, 7).is_err());
767        assert!(get(Unit::Millisecond, 1_000_000_000).is_err());
768        assert!(get(Unit::Millisecond, 1_000_000_001).is_err());
769        assert!(get(Unit::Millisecond, i64::MIN).is_err());
770        assert!(get(Unit::Millisecond, i64::MAX).is_err());
771
772        assert!(get(Unit::Second, 1).is_ok());
773        assert!(get(Unit::Second, 15).is_ok());
774        assert!(get(Unit::Second, 3_600).is_ok());
775        assert!(get(Unit::Second, 86_400).is_ok());
776        assert!(get(Unit::Second, -1).is_err());
777        assert!(get(Unit::Second, 0).is_err());
778        assert!(get(Unit::Second, 7).is_err());
779        assert!(get(Unit::Second, 1_000).is_err());
780        assert!(get(Unit::Second, 86_401).is_err());
781        assert!(get(Unit::Second, 172_800).is_err());
782        assert!(get(Unit::Second, 1_000_000_000).is_err());
783        assert!(get(Unit::Second, 1_000_000_001).is_err());
784        assert!(get(Unit::Second, i64::MIN).is_err());
785        assert!(get(Unit::Second, i64::MAX).is_err());
786
787        assert!(get(Unit::Minute, 1).is_ok());
788        assert!(get(Unit::Minute, 15).is_ok());
789        assert!(get(Unit::Minute, 1_440).is_ok());
790        assert!(get(Unit::Minute, -1).is_err());
791        assert!(get(Unit::Minute, 0).is_err());
792        assert!(get(Unit::Minute, 7).is_err());
793        assert!(get(Unit::Minute, 1_000).is_err());
794        assert!(get(Unit::Minute, 1_441).is_err());
795        assert!(get(Unit::Minute, 2_880).is_err());
796        assert!(get(Unit::Minute, 1_000_000_000).is_err());
797        assert!(get(Unit::Minute, 1_000_000_001).is_err());
798        assert!(get(Unit::Minute, i64::MIN).is_err());
799        assert!(get(Unit::Minute, i64::MAX).is_err());
800
801        assert!(get(Unit::Hour, 1).is_ok());
802        assert!(get(Unit::Hour, 6).is_ok());
803        assert!(get(Unit::Hour, 12).is_ok());
804        assert!(get(Unit::Hour, 24).is_ok());
805        assert!(get(Unit::Hour, -1).is_err());
806        assert!(get(Unit::Hour, 0).is_err());
807        assert!(get(Unit::Hour, 15).is_err());
808        assert!(get(Unit::Hour, 18).is_err());
809        assert!(get(Unit::Hour, 23).is_err());
810        assert!(get(Unit::Hour, 25).is_err());
811        assert!(get(Unit::Hour, 1_000_000_000).is_err());
812        assert!(get(Unit::Hour, 1_000_000_001).is_err());
813        assert!(get(Unit::Hour, i64::MIN).is_err());
814        assert!(get(Unit::Hour, i64::MAX).is_err());
815
816        assert!(get(Unit::Day, 1).is_err());
817        assert!(get(Unit::Day, -1).is_err());
818        assert!(get(Unit::Day, 0).is_err());
819        assert!(get(Unit::Day, 2).is_err());
820        assert!(get(Unit::Day, 4).is_err());
821        assert!(get(Unit::Day, 7).is_err());
822        assert!(get(Unit::Day, i64::MIN).is_err());
823        assert!(get(Unit::Day, i64::MAX).is_err());
824
825        assert!(get(Unit::Week, 1).is_err());
826        assert!(get(Unit::Week, -1).is_err());
827        assert!(get(Unit::Week, 0).is_err());
828        assert!(get(Unit::Week, 2).is_err());
829        assert!(get(Unit::Week, 4).is_err());
830        assert!(get(Unit::Week, 7).is_err());
831        assert!(get(Unit::Week, i64::MIN).is_err());
832        assert!(get(Unit::Week, i64::MAX).is_err());
833
834        assert!(get(Unit::Month, 1).is_err());
835        assert!(get(Unit::Month, -1).is_err());
836        assert!(get(Unit::Month, 0).is_err());
837        assert!(get(Unit::Month, 2).is_err());
838        assert!(get(Unit::Month, 4).is_err());
839        assert!(get(Unit::Month, 7).is_err());
840        assert!(get(Unit::Month, i64::MIN).is_err());
841        assert!(get(Unit::Month, i64::MAX).is_err());
842
843        assert!(get(Unit::Year, 1).is_err());
844        assert!(get(Unit::Year, -1).is_err());
845        assert!(get(Unit::Year, 0).is_err());
846        assert!(get(Unit::Year, 2).is_err());
847        assert!(get(Unit::Year, 4).is_err());
848        assert!(get(Unit::Year, 7).is_err());
849        assert!(get(Unit::Year, i64::MIN).is_err());
850        assert!(get(Unit::Year, i64::MAX).is_err());
851    }
852
853    // Some ad hoc tests I wrote while writing the rounding increment code.
854    #[test]
855    fn round_to_increment_half_expand_ad_hoc() {
856        let round = |quantity: i128, increment: i128| -> i128 {
857            RoundMode::HalfExpand.round_by_i128(quantity, increment)
858        };
859        assert_eq!(26, round(20, 13));
860
861        assert_eq!(0, round(29, 60));
862        assert_eq!(60, round(30, 60));
863        assert_eq!(60, round(31, 60));
864
865        assert_eq!(0, round(3, 7));
866        assert_eq!(7, round(4, 7));
867    }
868
869    // The Temporal tests are inspired by the table from here:
870    // https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement
871    //
872    // The main difference is that our rounding function specifically does not
873    // use floating point, so we tweak the values a bit.
874
875    #[test]
876    fn round_to_increment_temporal_table_ceil() {
877        let round = |quantity: i128, increment: i128| -> i128 {
878            RoundMode::Ceil.round_by_i128(quantity, increment)
879        };
880        assert_eq!(-10, round(-15, 10));
881        assert_eq!(0, round(-5, 10));
882        assert_eq!(10, round(4, 10));
883        assert_eq!(10, round(5, 10));
884        assert_eq!(10, round(6, 10));
885        assert_eq!(20, round(15, 10));
886
887        assert_eq!(i128::MAX, round(i128::MAX, 1));
888        assert_eq!(i128::MAX, round(i128::MAX, i128::MAX));
889
890        // TODO: This test currently panics on overflow.
891        // We should overall scrutinize the input constraints
892        // for our rounding routines and align with their call
893        // sites. We were previously relying on ranged integers
894        // to holistically verify we weren't doing anything that
895        // could result in overflow. I suspect that we can't
896        // actually support the i128 quantity range (and I don't
897        // think we need to). The question is whether we should
898        // make the rounding interface fallible or if this
899        // should just document panicking conditions.
900        //
901        // Oh, we should introduce a new internal `Increment`
902        // wrapper that provides guarantees about its range.
903        // assert_eq!(i128::MIN, round(i128::MIN, -1));
904        // assert_eq!(i128::MIN, round(i128::MIN, i128::MIN));
905    }
906
907    #[test]
908    fn round_to_increment_temporal_table_floor() {
909        let round = |quantity: i128, increment: i128| -> i128 {
910            RoundMode::Floor.round_by_i128(quantity, increment)
911        };
912        assert_eq!(-20, round(-15, 10));
913        assert_eq!(-10, round(-5, 10));
914        assert_eq!(0, round(4, 10));
915        assert_eq!(0, round(5, 10));
916        assert_eq!(0, round(6, 10));
917        assert_eq!(10, round(15, 10));
918    }
919
920    #[test]
921    fn round_to_increment_temporal_table_expand() {
922        let round = |quantity: i128, increment: i128| -> i128 {
923            RoundMode::Expand.round_by_i128(quantity, increment)
924        };
925        assert_eq!(-20, round(-15, 10));
926        assert_eq!(-10, round(-5, 10));
927        assert_eq!(10, round(4, 10));
928        assert_eq!(10, round(5, 10));
929        assert_eq!(10, round(6, 10));
930        assert_eq!(20, round(15, 10));
931    }
932
933    #[test]
934    fn round_to_increment_temporal_table_trunc() {
935        let round = |quantity: i128, increment: i128| -> i128 {
936            RoundMode::Trunc.round_by_i128(quantity, increment)
937        };
938        assert_eq!(-10, round(-15, 10));
939        assert_eq!(0, round(-5, 10));
940        assert_eq!(0, round(4, 10));
941        assert_eq!(0, round(5, 10));
942        assert_eq!(0, round(6, 10));
943        assert_eq!(10, round(15, 10));
944    }
945
946    #[test]
947    fn round_to_increment_temporal_table_half_ceil() {
948        let round = |quantity: i128, increment: i128| -> i128 {
949            RoundMode::HalfCeil.round_by_i128(quantity, increment)
950        };
951        assert_eq!(-10, round(-15, 10));
952        assert_eq!(0, round(-5, 10));
953        assert_eq!(0, round(4, 10));
954        assert_eq!(10, round(5, 10));
955        assert_eq!(10, round(6, 10));
956        assert_eq!(20, round(15, 10));
957    }
958
959    #[test]
960    fn round_to_increment_temporal_table_half_floor() {
961        let round = |quantity: i128, increment: i128| -> i128 {
962            RoundMode::HalfFloor.round_by_i128(quantity, increment)
963        };
964        assert_eq!(-20, round(-15, 10));
965        assert_eq!(-10, round(-5, 10));
966        assert_eq!(0, round(4, 10));
967        assert_eq!(0, round(5, 10));
968        assert_eq!(10, round(6, 10));
969        assert_eq!(10, round(15, 10));
970    }
971
972    #[test]
973    fn round_to_increment_temporal_table_half_expand() {
974        let round = |quantity: i128, increment: i128| -> i128 {
975            RoundMode::HalfExpand.round_by_i128(quantity, increment)
976        };
977        assert_eq!(-20, round(-15, 10));
978        assert_eq!(-10, round(-5, 10));
979        assert_eq!(0, round(4, 10));
980        assert_eq!(10, round(5, 10));
981        assert_eq!(10, round(6, 10));
982        assert_eq!(20, round(15, 10));
983    }
984
985    #[test]
986    fn round_to_increment_temporal_table_half_trunc() {
987        let round = |quantity: i128, increment: i128| -> i128 {
988            RoundMode::HalfTrunc.round_by_i128(quantity, increment)
989        };
990        assert_eq!(-10, round(-15, 10));
991        assert_eq!(0, round(-5, 10));
992        assert_eq!(0, round(4, 10));
993        assert_eq!(0, round(5, 10));
994        assert_eq!(10, round(6, 10));
995        assert_eq!(10, round(15, 10));
996    }
997
998    #[test]
999    fn round_to_increment_temporal_table_half_even() {
1000        let round = |quantity: i128, increment: i128| -> i128 {
1001            RoundMode::HalfEven.round_by_i128(quantity, increment)
1002        };
1003        assert_eq!(-20, round(-15, 10));
1004        assert_eq!(0, round(-5, 10));
1005        assert_eq!(0, round(4, 10));
1006        assert_eq!(0, round(5, 10));
1007        assert_eq!(10, round(6, 10));
1008        assert_eq!(20, round(15, 10));
1009    }
1010
1011    // Tests that the maximum possible increment, in units of nanoseconds,
1012    // fits into a `SignedDuration`.
1013    #[test]
1014    fn maximum_increment_nanos() {
1015        assert!(Increment::for_span(Unit::Week, 1_000_000_000).is_ok());
1016        assert!(Increment::for_span(Unit::Week, 1_000_000_001).is_err());
1017        assert_eq!(
1018            Unit::Week.duration() * 1_000_000_000,
1019            SignedDuration::from_secs(168_000_000_000 * 60 * 60)
1020        );
1021    }
1022
1023    // Tests some edge cases where rounding can actually overflow. We ensure
1024    // that an error is returned instead of panicking.
1025    //
1026    // I do not believe it's possible for these overflows to be observed using
1027    // Jiff's public API however.
1028    #[test]
1029    fn round_by_duration_overflow() {
1030        let mode = RoundMode::Expand;
1031        let quantity = SignedDuration::MAX;
1032        let increment = SignedDuration::MAX - SignedDuration::from_secs(1);
1033        assert!(mode.round_by_duration(quantity, increment).is_err());
1034
1035        let mode = RoundMode::Expand;
1036        let quantity = SignedDuration::MIN;
1037        let increment = SignedDuration::MAX;
1038        assert!(mode.round_by_duration(quantity, increment).is_err());
1039    }
1040}