Skip to main content

mz_repr/adt/
interval.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! A time interval abstract data type.
11
12use std::fmt::{self, Write};
13use std::sync::LazyLock;
14use std::time::Duration;
15
16use anyhow::{anyhow, bail};
17use mz_persist_types::columnar::FixedSizeCodec;
18use mz_proto::{RustType, TryFromProtoError};
19use num_traits::CheckedMul;
20#[cfg(any(test, feature = "proptest"))]
21use proptest::prelude::{Arbitrary, BoxedStrategy, Strategy, any};
22use serde::{Deserialize, Serialize};
23
24use crate::adt::datetime::DateTimeField;
25use crate::adt::numeric::{DecimalLike, Numeric};
26use crate::strconv;
27
28include!(concat!(env!("OUT_DIR"), "/mz_repr.adt.interval.rs"));
29
30/// An interval of time meant to express SQL intervals.
31///
32/// Obtained by parsing an `INTERVAL '<value>' <unit> [TO <precision>]`.
33#[derive(
34    Debug,
35    Clone,
36    Copy,
37    PartialEq,
38    Eq,
39    PartialOrd,
40    Ord,
41    Serialize,
42    Hash,
43    Deserialize
44)]
45pub struct Interval {
46    /// A possibly negative number of months for field types like `YEAR`
47    pub months: i32,
48    /// A possibly negative number of days.
49    ///
50    /// Irrespective of values, `days` will not be carried over into `months`.
51    pub days: i32,
52    /// A timespan represented in microseconds.
53    ///
54    /// Irrespective of values, `micros` will not be carried over into `days` or
55    /// `months`.
56    pub micros: i64,
57}
58
59impl Default for Interval {
60    fn default() -> Self {
61        Self {
62            months: 0,
63            days: 0,
64            micros: 0,
65        }
66    }
67}
68
69impl RustType<ProtoInterval> for Interval {
70    fn into_proto(&self) -> ProtoInterval {
71        ProtoInterval {
72            months: self.months,
73            days: self.days,
74            micros: self.micros,
75        }
76    }
77
78    fn from_proto(proto: ProtoInterval) -> Result<Self, TryFromProtoError> {
79        Ok(Interval {
80            months: proto.months,
81            days: proto.days,
82            micros: proto.micros,
83        })
84    }
85}
86
87impl num_traits::ops::checked::CheckedNeg for Interval {
88    fn checked_neg(&self) -> Option<Self> {
89        if let (Some(months), Some(days), Some(micros)) = (
90            self.months.checked_neg(),
91            self.days.checked_neg(),
92            self.micros.checked_neg(),
93        ) {
94            Some(Self::new(months, days, micros))
95        } else {
96            None
97        }
98    }
99}
100
101impl std::str::FromStr for Interval {
102    type Err = anyhow::Error;
103
104    fn from_str(s: &str) -> Result<Self, Self::Err> {
105        crate::strconv::parse_interval(s).map_err(|e| anyhow!(e))
106    }
107}
108
109static MONTH_OVERFLOW_ERROR: LazyLock<String> = LazyLock::new(|| {
110    format!(
111        "Overflows maximum months; cannot exceed {}/{} microseconds",
112        i32::MAX,
113        i32::MIN,
114    )
115});
116static DAY_OVERFLOW_ERROR: LazyLock<String> = LazyLock::new(|| {
117    format!(
118        "Overflows maximum days; cannot exceed {}/{} microseconds",
119        i32::MAX,
120        i32::MIN,
121    )
122});
123pub static USECS_PER_DAY: LazyLock<i64> = LazyLock::new(|| {
124    Interval::convert_date_time_unit(DateTimeField::Day, DateTimeField::Microseconds, 1i64).unwrap()
125});
126
127#[derive(Debug, Clone)]
128pub enum RoundBehavior {
129    Truncate,
130    Nearest,
131}
132
133impl Interval {
134    pub const CENTURY_PER_MILLENNIUM: u16 = 10;
135    pub const DECADE_PER_CENTURY: u16 = 10;
136    pub const YEAR_PER_DECADE: u16 = 10;
137    pub const MONTH_PER_YEAR: u16 = 12;
138    // Interval type considers 30 days == 1 month
139    pub const DAY_PER_MONTH: u16 = 30;
140    // Interval type considers 24 hours == 1 day
141    pub const HOUR_PER_DAY: u16 = 24;
142    pub const MINUTE_PER_HOUR: u16 = 60;
143    pub const SECOND_PER_MINUTE: u16 = 60;
144    pub const MILLISECOND_PER_SECOND: u16 = 1_000;
145    pub const MICROSECOND_PER_MILLISECOND: u16 = 1_000;
146    pub const NANOSECOND_PER_MICROSECOND: u16 = 1_000;
147    // PostgreSQL actually has a bug where when using EXTRACT it truncates this value to 365, but
148    // when using date_part it does not truncate this value. Therefore our EXTRACT function may differ
149    // from PostgreSQL.
150    // EXTRACT: https://github.com/postgres/postgres/blob/c2e8bd27519f47ff56987b30eb34a01969b9a9e8/src/backend/utils/adt/timestamp.c#L5270-L5273
151    // date_part: https://github.com/postgres/postgres/blob/c2e8bd27519f47ff56987b30eb34a01969b9a9e8/src/backend/utils/adt/timestamp.c#L5301
152    pub const EPOCH_DAYS_PER_YEAR: f64 = 365.25;
153
154    /// Constructs a new `Interval` with the specified units of time.
155    pub const fn new(months: i32, days: i32, micros: i64) -> Interval {
156        Interval {
157            months,
158            days,
159            micros,
160        }
161    }
162
163    /// Constructs a new `Interval` from component counts held in a wider type,
164    /// returning `None` if any of them overflows its field.
165    ///
166    /// This is the narrowing step for accumulations that sum the three
167    /// components independently in `i128`, as `sum(interval)` does.
168    pub fn try_new(months: i128, days: i128, micros: i128) -> Option<Interval> {
169        Some(Interval::new(
170            i32::try_from(months).ok()?,
171            i32::try_from(days).ok()?,
172            i64::try_from(micros).ok()?,
173        ))
174    }
175
176    /// Converts a `Duration` to an `Interval`. The resulting `Interval` will only have
177    /// microseconds. Errors if
178    /// - the number of microseconds doesn't fit in i64, or
179    /// - the `Duration` involves fractional microseconds.
180    pub fn from_duration(duration: &Duration) -> Result<Interval, anyhow::Error> {
181        if duration.subsec_nanos() % 1000 != 0 {
182            return Err(anyhow!(
183                "cannot convert Duration to Interval due to fractional microseconds"
184            ));
185        }
186        Ok(Interval {
187            months: 0,
188            days: 0,
189            micros: duration.as_micros().try_into()?,
190        })
191    }
192
193    /// Converts the parts of an `INTERVAL '...'` literal into an `Interval`.
194    ///
195    /// `precision_high` and `precision_low` are the range qualifier, as in
196    /// `INTERVAL '1:30' MINUTE TO SECOND`, and `fsec_max_precision` is the
197    /// fractional second precision of `SECOND(n)`.
198    pub fn from_literal(
199        value: &str,
200        precision_high: DateTimeField,
201        precision_low: DateTimeField,
202        fsec_max_precision: Option<u64>,
203    ) -> Result<Interval, anyhow::Error> {
204        let mut interval = strconv::parse_interval_w_disambiguator(
205            value,
206            match precision_high {
207                DateTimeField::Hour | DateTimeField::Minute => Some(precision_high),
208                _ => None,
209            },
210            precision_low,
211        )?;
212        // Like PostgreSQL, the range qualifier only disambiguates parsing and
213        // discards fields below its low end. Fields above its high end are kept
214        // as written.
215        interval.truncate_low_fields(precision_low, fsec_max_precision, RoundBehavior::Nearest)?;
216        Ok(interval)
217    }
218
219    /// Converts a `chrono::Duration` to an `Interval`. The resulting `Interval` will only have
220    /// microseconds, with the nanoseconds truncated.
221    pub fn from_chrono_duration(duration: chrono::Duration) -> Result<Self, anyhow::Error> {
222        let Some(micros) = duration.num_microseconds() else {
223            bail!("cannot convert Duration to Interval due to overflowed microseconds");
224        };
225        Ok(Self {
226            months: 0,
227            days: 0,
228            micros,
229        })
230    }
231
232    pub fn checked_add(&self, other: &Self) -> Option<Self> {
233        let months = match self.months.checked_add(other.months) {
234            Some(m) => m,
235            None => return None,
236        };
237        let days = match self.days.checked_add(other.days) {
238            Some(d) => d,
239            None => return None,
240        };
241        let micros = match self.micros.checked_add(other.micros) {
242            Some(us) => us,
243            None => return None,
244        };
245
246        Some(Self::new(months, days, micros))
247    }
248
249    pub fn checked_mul(&self, other: f64) -> Option<Self> {
250        self.checked_op(other, |f1, f2| f1 * f2)
251    }
252
253    pub fn checked_div(&self, other: f64) -> Option<Self> {
254        self.checked_op(other, |f1, f2| f1 / f2)
255    }
256
257    // TODO(benesch): the use of `as` in this function looks very sketchy.
258    // Rewrite.
259    #[allow(clippy::as_conversions)]
260    fn checked_op<F1>(&self, other: f64, op: F1) -> Option<Self>
261    where
262        F1: Fn(f64, f64) -> f64,
263    {
264        let months = op(f64::from(self.months), other);
265        if months.is_nan()
266            || months.is_infinite()
267            || months < i32::MIN.into()
268            || months > i32::MAX.into()
269        {
270            return None;
271        }
272
273        let days =
274            op(f64::from(self.days), other) + months.fract() * f64::from(Self::DAY_PER_MONTH);
275        if days.is_nan() || days.is_infinite() || days < i32::MIN.into() || days > i32::MAX.into() {
276            return None;
277        }
278
279        let micros = op(self.micros as f64, other)
280            + days.fract()
281                * f64::from(Self::HOUR_PER_DAY)
282                * f64::from(Self::MINUTE_PER_HOUR)
283                * f64::from(Self::SECOND_PER_MINUTE)
284                * f64::from(Self::MILLISECOND_PER_SECOND)
285                * f64::from(Self::MICROSECOND_PER_MILLISECOND);
286
287        if micros.is_nan()
288            || micros.is_infinite()
289            || Numeric::from(micros) < Numeric::from(i64::MIN)
290            || Numeric::from(micros) > Numeric::from(i64::MAX)
291        {
292            return None;
293        }
294
295        Some(Self::new(months as i32, days as i32, micros as i64))
296    }
297
298    /// Computes the millennium part of the interval.
299    ///
300    /// The millennium part is the number of whole millennia in the interval. For example,
301    /// this function returns `3` for the interval `3400 years`.
302    pub fn millennia(&self) -> i32 {
303        Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Millennium, self.months)
304            .unwrap()
305    }
306
307    /// Computes the century part of the interval.
308    ///
309    /// The century part is the number of whole centuries in the interval. For example,
310    /// this function returns `3` for the interval `340 years`.
311    pub fn centuries(&self) -> i32 {
312        Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Century, self.months)
313            .unwrap()
314    }
315
316    /// Computes the decade part of the interval.
317    ///
318    /// The decade part is the number of whole decades in the interval. For example,
319    /// this function returns `3` for the interval `34 years`.
320    pub fn decades(&self) -> i32 {
321        Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Decade, self.months)
322            .unwrap()
323    }
324
325    /// Computes the year part of the interval.
326    ///
327    /// The year part is the number of whole years in the interval. For example,
328    /// this function returns `3` for the interval `3 years 4 months`.
329    pub fn years(&self) -> i32 {
330        Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Year, self.months)
331            .unwrap()
332    }
333
334    /// Computes the quarter part of the interval.
335    ///
336    /// The quarter part is obtained from taking the number of whole months modulo 12,
337    /// and assigning quarter #1 for months 0-2, #2 for 3-5, #3 for 6-8 and #4 for 9-11.
338    /// For example, this function returns `4` for the interval `11 months`.
339    pub fn quarters(&self) -> i32 {
340        self.months() / 3 + 1
341    }
342
343    /// Computes the month part of the interval.
344    ///
345    /// The month part is the number of whole months in the interval, modulo 12.
346    /// For example, this function returns `4` for the interval `3 years 4
347    /// months`.
348    pub fn months(&self) -> i32 {
349        self.months % i32::from(Self::MONTH_PER_YEAR)
350    }
351
352    /// Computes the day part of the interval.
353    ///
354    /// The day part is the number of whole days in the interval. For example,
355    /// this function returns `5` for the interval `5 days 4 hours 3 minutes
356    /// 2.1 seconds`.
357    pub fn days(&self) -> i64 {
358        self.days.into()
359    }
360
361    /// Computes the hour part of the interval.
362    ///
363    /// The hour part is the number of whole hours in the interval, modulo 24.
364    /// For example, this function returns `4` for the interval `5 days 4
365    /// hours 3 minutes 2.1 seconds`.
366    pub fn hours(&self) -> i64 {
367        Self::convert_date_time_unit(
368            DateTimeField::Microseconds,
369            DateTimeField::Hour,
370            self.micros,
371        )
372        .unwrap()
373            % i64::from(Self::HOUR_PER_DAY)
374    }
375
376    /// Computes the minute part of the interval.
377    ///
378    /// The minute part is the number of whole minutes in the interval, modulo
379    /// 60. For example, this function returns `3` for the interval `5 days 4
380    /// hours 3 minutes 2.1 seconds`.
381    pub fn minutes(&self) -> i64 {
382        Self::convert_date_time_unit(
383            DateTimeField::Microseconds,
384            DateTimeField::Minute,
385            self.micros,
386        )
387        .unwrap()
388            % i64::from(Self::MINUTE_PER_HOUR)
389    }
390
391    /// Computes the second part of the interval.
392    ///
393    /// The second part is the number of fractional seconds in the interval,
394    /// modulo 60.0.
395    pub fn seconds<T>(&self) -> T
396    where
397        T: DecimalLike,
398    {
399        T::lossy_from(self.micros % 60_000_000) / T::from(1e6)
400    }
401
402    /// Computes the second part of the interval displayed in milliseconds.
403    ///
404    /// The second part is the number of fractional seconds in the interval,
405    /// modulo 60.0.
406    pub fn milliseconds<T>(&self) -> T
407    where
408        T: DecimalLike,
409    {
410        T::lossy_from(self.micros % 60_000_000) / T::from(1e3)
411    }
412
413    /// Computes the second part of the interval displayed in microseconds.
414    ///
415    /// The second part is the number of fractional seconds in the interval,
416    /// modulo 60.0.
417    pub fn microseconds<T>(&self) -> T
418    where
419        T: DecimalLike,
420    {
421        T::lossy_from(self.micros % 60_000_000)
422    }
423
424    /// Computes the nanosecond part of the interval.
425    pub fn nanoseconds(&self) -> i32 {
426        (self.micros % 1_000_000 * 1_000).try_into().unwrap()
427    }
428
429    /// Computes the total number of epoch seconds in the interval.
430    /// When extracting an epoch, PostgreSQL considers a year
431    /// 365.25 days.
432    pub fn as_epoch_seconds<T>(&self) -> T
433    where
434        T: DecimalLike,
435    {
436        let days = T::from(self.years()) * T::from(Self::EPOCH_DAYS_PER_YEAR)
437            + T::from(self.months()) * T::from(Self::DAY_PER_MONTH)
438            + T::from(self.days);
439        let seconds = days
440            * T::from(Self::HOUR_PER_DAY)
441            * T::from(Self::MINUTE_PER_HOUR)
442            * T::from(Self::SECOND_PER_MINUTE);
443
444        seconds
445            + T::lossy_from(self.micros)
446                / (T::from(Self::MICROSECOND_PER_MILLISECOND)
447                    * T::from(Self::MILLISECOND_PER_SECOND))
448    }
449
450    /// Computes the total number of microseconds in the interval.
451    pub fn as_microseconds(&self) -> i128 {
452        // unwrap is safe because i32::MAX/i32::MIN number of months will not overflow an i128 when
453        // converted to microseconds.
454        Self::convert_date_time_unit(
455            DateTimeField::Month,
456            DateTimeField::Microseconds,
457            i128::from(self.months),
458        ).unwrap() +
459        // unwrap is safe because i32::MAX/i32::MIN number of days will not overflow an i128 when
460        // converted to microseconds.
461        Self::convert_date_time_unit(
462            DateTimeField::Day,
463            DateTimeField::Microseconds,
464            i128::from(self.days),
465        ).unwrap() +
466        i128::from(self.micros)
467    }
468
469    /// Computes the total number of milliseconds in the interval. Discards fractional milliseconds!
470    pub fn as_milliseconds(&self) -> i128 {
471        self.as_microseconds() / 1000
472    }
473
474    /// Converts this `Interval`'s duration into `chrono::Duration`.
475    pub fn duration_as_chrono(&self) -> chrono::Duration {
476        use chrono::Duration;
477        Duration::try_days(self.days.into()).unwrap() + Duration::microseconds(self.micros)
478    }
479
480    pub fn duration(&self) -> Result<Duration, anyhow::Error> {
481        if self.months != 0 {
482            bail!("cannot convert interval with months to duration");
483        }
484        if self.is_negative() {
485            bail!("cannot convert negative interval to duration");
486        }
487        // Phrase the overflow ourselves rather than letting `TryFromIntError`
488        // through. Its `Display` is std's, and std has reworded it between
489        // toolchains, which would otherwise rewrite a user-facing error message
490        // out from under us.
491        let Ok(micros) = u64::try_from(self.as_microseconds()) else {
492            bail!("interval is too large to convert to duration");
493        };
494        Ok(Duration::from_micros(micros))
495    }
496
497    /// Truncate the "tail" of the interval, removing all time units less than `f`.
498    /// # Arguments
499    /// - `f`: Round the interval down to the specified time unit.
500    /// - `fsec_max_precision`: If `Some(x)`, keep only `x` places of microsecond precision.
501    ///    Must be `(0,6)`.
502    ///
503    /// # Errors
504    /// - If `fsec_max_precision` is not None or within (0,6).
505    pub fn truncate_low_fields(
506        &mut self,
507        f: DateTimeField,
508        fsec_max_precision: Option<u64>,
509        round_behavior: RoundBehavior,
510    ) -> Result<(), anyhow::Error> {
511        use DateTimeField::*;
512        match f {
513            Millennium => {
514                self.months -= self.months % (12 * 1000);
515                self.days = 0;
516                self.micros = 0;
517            }
518            Century => {
519                self.months -= self.months % (12 * 100);
520                self.days = 0;
521                self.micros = 0;
522            }
523            Decade => {
524                self.months -= self.months % (12 * 10);
525                self.days = 0;
526                self.micros = 0;
527            }
528            Year => {
529                self.months -= self.months % 12;
530                self.days = 0;
531                self.micros = 0;
532            }
533            Month => {
534                self.days = 0;
535                self.micros = 0;
536            }
537            // Round microseconds.
538            Second => {
539                let default_precision = 6;
540                let precision = match fsec_max_precision {
541                    Some(p) => p,
542                    None => default_precision,
543                };
544
545                if precision > default_precision {
546                    bail!(
547                        "SECOND precision must be (0, 6), have SECOND({})",
548                        precision
549                    )
550                }
551
552                let precision = match u32::try_from(precision) {
553                    Ok(p) => p,
554                    Err(_) => bail!(
555                        "SECOND precision must be (0, 6), have SECOND({})",
556                        precision
557                    ),
558                };
559                // Truncate sub-second part.
560                let remainder = self.micros % 10_i64.pow(6 - precision);
561                self.micros -= remainder;
562                // Check if value should round up/down to nearest fractional place.
563                if matches!(round_behavior, RoundBehavior::Nearest)
564                    && u64::from(precision) != default_precision
565                {
566                    let rounding_digit = remainder / 10_i64.pow(5 - precision);
567                    let micros = if rounding_digit > 4 {
568                        self.micros.checked_add(10_i64.pow(6 - precision))
569                    } else if rounding_digit < -4 {
570                        self.micros.checked_sub(10_i64.pow(6 - precision))
571                    } else {
572                        Some(self.micros)
573                    };
574                    let Some(micros) = micros else {
575                        bail!("interval field value out of range: \"{self}\"");
576                    };
577                    self.micros = micros;
578                }
579            }
580            Day => {
581                self.micros = 0;
582            }
583            Hour | Minute | Milliseconds | Microseconds => {
584                self.micros -= self.micros % f.micros_multiplier();
585            }
586        }
587        Ok(())
588    }
589
590    /// Returns a new Interval with only the time component
591    pub fn as_time_interval(&self) -> Self {
592        Self::new(0, 0, self.micros)
593    }
594
595    /// Returns true if combining all fields results in a negative number, false otherwise
596    pub fn is_negative(&self) -> bool {
597        self.as_microseconds() < 0
598    }
599
600    /// Convert val from source unit to dest unit. Does not maintain fractional values.
601    /// Returns None if the result overflows/underflows.
602    ///
603    /// WARNING: Due to the fact that Intervals consider months to have 30 days, you may get
604    /// unexpected and incorrect results when trying to convert from a non-year type to a year type
605    /// and vice versa. For example from years to days.
606    pub fn convert_date_time_unit<T>(
607        source: DateTimeField,
608        dest: DateTimeField,
609        val: T,
610    ) -> Option<T>
611    where
612        T: From<u16> + CheckedMul + std::ops::DivAssign,
613    {
614        if source < dest {
615            Self::convert_date_time_unit_increasing(source, dest, val)
616        } else if source > dest {
617            Self::convert_date_time_unit_decreasing(source, dest, val)
618        } else {
619            Some(val)
620        }
621    }
622
623    fn convert_date_time_unit_increasing<T>(
624        source: DateTimeField,
625        dest: DateTimeField,
626        val: T,
627    ) -> Option<T>
628    where
629        T: From<u16> + std::ops::DivAssign,
630    {
631        let mut cur_unit = source;
632        let mut res = val;
633        while cur_unit < dest {
634            let divisor: T = match cur_unit {
635                DateTimeField::Millennium => 1.into(),
636                DateTimeField::Century => Self::CENTURY_PER_MILLENNIUM.into(),
637                DateTimeField::Decade => Self::DECADE_PER_CENTURY.into(),
638                DateTimeField::Year => Self::YEAR_PER_DECADE.into(),
639                DateTimeField::Month => Self::MONTH_PER_YEAR.into(),
640                DateTimeField::Day => Self::DAY_PER_MONTH.into(),
641                DateTimeField::Hour => Self::HOUR_PER_DAY.into(),
642                DateTimeField::Minute => Self::MINUTE_PER_HOUR.into(),
643                DateTimeField::Second => Self::SECOND_PER_MINUTE.into(),
644                DateTimeField::Milliseconds => Self::MILLISECOND_PER_SECOND.into(),
645                DateTimeField::Microseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
646            };
647            res /= divisor;
648            cur_unit = cur_unit.next_largest();
649        }
650
651        Some(res)
652    }
653
654    fn convert_date_time_unit_decreasing<T>(
655        source: DateTimeField,
656        dest: DateTimeField,
657        val: T,
658    ) -> Option<T>
659    where
660        T: From<u16> + CheckedMul,
661    {
662        let mut cur_unit = source;
663        let mut res = val;
664        while cur_unit > dest {
665            let multiplier: T = match cur_unit {
666                DateTimeField::Millennium => Self::CENTURY_PER_MILLENNIUM.into(),
667                DateTimeField::Century => Self::DECADE_PER_CENTURY.into(),
668                DateTimeField::Decade => Self::YEAR_PER_DECADE.into(),
669                DateTimeField::Year => Self::MONTH_PER_YEAR.into(),
670                DateTimeField::Month => Self::DAY_PER_MONTH.into(),
671                DateTimeField::Day => Self::HOUR_PER_DAY.into(),
672                DateTimeField::Hour => Self::MINUTE_PER_HOUR.into(),
673                DateTimeField::Minute => Self::SECOND_PER_MINUTE.into(),
674                DateTimeField::Second => Self::MILLISECOND_PER_SECOND.into(),
675                DateTimeField::Milliseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
676                DateTimeField::Microseconds => 1.into(),
677            };
678            res = match res.checked_mul(&multiplier) {
679                Some(r) => r,
680                None => return None,
681            };
682            cur_unit = cur_unit.next_smallest();
683        }
684
685        Some(res)
686    }
687
688    /// Adjust interval so 'days' contains less than 30 days, adding the excess to 'months'.
689    pub fn justify_days(&self) -> Result<Self, anyhow::Error> {
690        let days_per_month = i32::from(Self::DAY_PER_MONTH);
691        let (mut months, mut days) = Self::justify_days_inner(self.months, self.days)?;
692        if months > 0 && days < 0 {
693            days += days_per_month;
694            months -= 1;
695        } else if months < 0 && days > 0 {
696            days -= days_per_month;
697            months += 1;
698        }
699
700        Ok(Self::new(months, days, self.micros))
701    }
702
703    fn justify_days_inner(months: i32, days: i32) -> Result<(i32, i32), anyhow::Error> {
704        let days_per_month = i32::from(Self::DAY_PER_MONTH);
705        let whole_month = days / days_per_month;
706        let days = days - whole_month * days_per_month;
707
708        let months = months
709            .checked_add(whole_month)
710            .ok_or_else(|| anyhow!(&*MONTH_OVERFLOW_ERROR))?;
711
712        Ok((months, days))
713    }
714
715    /// Adjust interval so 'micros' contains less than a whole day, adding the excess to 'days'.
716    pub fn justify_hours(&self) -> Result<Self, anyhow::Error> {
717        let (mut days, mut micros) = Self::justify_hours_inner(self.days, self.micros)?;
718        if days > 0 && micros < 0 {
719            micros += &*USECS_PER_DAY;
720            days -= 1;
721        } else if days < 0 && micros > 0 {
722            micros -= &*USECS_PER_DAY;
723            days += 1;
724        }
725
726        Ok(Self::new(self.months, days, micros))
727    }
728
729    fn justify_hours_inner(days: i32, micros: i64) -> Result<(i32, i64), anyhow::Error> {
730        let days = i32::try_from(micros / &*USECS_PER_DAY)
731            .ok()
732            .and_then(|d| days.checked_add(d))
733            .ok_or_else(|| anyhow!(&*DAY_OVERFLOW_ERROR))?;
734        let micros = micros % &*USECS_PER_DAY;
735
736        Ok((days, micros))
737    }
738
739    /// Adjust interval so 'days' contains less than 30 days, adding the excess to 'months'.
740    /// Adjust interval so 'micros' contains less than a whole day, adding the excess to 'days'.
741    /// Also, the sign bit on all three fields is made equal, so either all three fields are negative or all are positive.
742    pub fn justify_interval(&self) -> Result<Self, anyhow::Error> {
743        let days_per_month = i32::from(Self::DAY_PER_MONTH);
744        let mut months = self.months;
745        let mut days = self.days;
746        let micros = self.micros;
747        // We justify days twice to try to avoid an intermediate overflow of days if it would be
748        // able to fit in months.
749        if (days > 0 && micros > 0) || (days < 0 && micros < 0) {
750            let (m, d) = Self::justify_days_inner(self.months, self.days)?;
751            months = m;
752            days = d;
753        }
754        let (days, mut micros) = Self::justify_hours_inner(days, micros)?;
755        let (mut months, mut days) = Self::justify_days_inner(months, days)?;
756
757        if months > 0 && (days < 0 || (days == 0 && micros < 0)) {
758            days += days_per_month;
759            months -= 1;
760        } else if months < 0 && (days > 0 || (days == 0 && micros > 0)) {
761            days -= days_per_month;
762            months += 1;
763        }
764
765        if days > 0 && micros < 0 {
766            micros += &*USECS_PER_DAY;
767            days -= 1;
768        } else if days < 0 && micros > 0 {
769            micros -= &*USECS_PER_DAY;
770            days += 1;
771        }
772
773        Ok(Self::new(months, days, micros))
774    }
775}
776
777/// Format an interval in a human form
778///
779/// Example outputs:
780///
781/// * 1 year 2 mons 5 days 03:04:00
782/// * -1 year +5 days +18:59:29.3
783/// * 00:00:00
784impl fmt::Display for Interval {
785    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
786        let neg_months = self.months < 0;
787        let years = (self.months / 12).abs();
788        let months = (self.months % 12).abs();
789
790        let neg_days = self.days < 0;
791        let days = i64::from(self.days).abs();
792
793        let mut nanos = self.nanoseconds().abs();
794        let mut secs = (self.micros / 1_000_000).abs();
795
796        let sec_per_hr = 60 * 60;
797        let hours = secs / sec_per_hr;
798        secs %= sec_per_hr;
799
800        let sec_per_min = 60;
801        let minutes = secs / sec_per_min;
802        secs %= sec_per_min;
803
804        if years > 0 {
805            if neg_months {
806                f.write_char('-')?;
807            }
808            write!(f, "{} year", years)?;
809            if years > 1 || neg_months {
810                f.write_char('s')?;
811            }
812        }
813
814        if months > 0 {
815            if years != 0 {
816                f.write_char(' ')?;
817            }
818            if neg_months {
819                f.write_char('-')?;
820            }
821            write!(f, "{} mon", months)?;
822            if months > 1 || neg_months {
823                f.write_char('s')?;
824            }
825        }
826
827        if days != 0 {
828            if years > 0 || months > 0 {
829                f.write_char(' ')?;
830            }
831            if neg_months && !neg_days {
832                f.write_char('+')?;
833            }
834            write!(f, "{} day", self.days)?;
835            if self.days != 1 {
836                f.write_char('s')?;
837            }
838        }
839
840        let non_zero_hmsn = hours > 0 || minutes > 0 || secs > 0 || nanos > 0;
841
842        if (years == 0 && months == 0 && days == 0) || non_zero_hmsn {
843            if years > 0 || months > 0 || days > 0 {
844                f.write_char(' ')?;
845            }
846            if self.micros < 0 && non_zero_hmsn {
847                f.write_char('-')?;
848            } else if neg_days || (days == 0 && neg_months) {
849                f.write_char('+')?;
850            }
851            write!(f, "{:02}:{:02}:{:02}", hours, minutes, secs)?;
852            if nanos > 0 {
853                let mut width = 9;
854                while nanos % 10 == 0 {
855                    width -= 1;
856                    nanos /= 10;
857                }
858                write!(f, ".{:0width$}", nanos, width = width)?;
859            }
860        }
861
862        Ok(())
863    }
864}
865
866#[cfg(any(test, feature = "proptest"))]
867impl Arbitrary for Interval {
868    type Strategy = BoxedStrategy<Self>;
869    type Parameters = ();
870
871    fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
872        (
873            any::<i32>(),
874            any::<i32>(),
875            ((((i64::from(i32::MIN) * 60) - 59) * 60) * 1_000_000 - 59_999_999
876                ..(((i64::from(i32::MAX) * 60) + 59) * 60) * 1_000_000 + 59_999_999),
877        )
878            .prop_map(|(months, days, micros)| Interval {
879                months,
880                days,
881                micros,
882            })
883            .boxed()
884    }
885}
886
887/// An encoded packed variant of [`Interval`].
888///
889/// We uphold the variant that [`PackedInterval`] sorts the same as [`Interval`].
890#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
891pub struct PackedInterval([u8; Self::SIZE]);
892
893// `as` conversions are okay here because we're doing bit level logic to make
894// sure the sort order of the packed binary is correct. This is implementation
895// is proptest-ed below.
896#[allow(clippy::as_conversions)]
897impl FixedSizeCodec<Interval> for PackedInterval {
898    const SIZE: usize = 16;
899
900    fn as_bytes(&self) -> &[u8] {
901        &self.0[..]
902    }
903
904    fn from_bytes(slice: &[u8]) -> Result<Self, String> {
905        let buf: [u8; Self::SIZE] = slice.try_into().map_err(|_| {
906            format!(
907                "size for PackedInterval is {} bytes, got {}",
908                Self::SIZE,
909                slice.len()
910            )
911        })?;
912        Ok(PackedInterval(buf))
913    }
914
915    #[inline]
916    fn from_value(value: Interval) -> Self {
917        let mut buf = [0u8; 16];
918
919        // Note: We XOR the values to get correct sorting of negative values.
920
921        let months = (value.months as u32) ^ (0x8000_0000u32);
922        let days = (value.days as u32) ^ (0x8000_0000u32);
923        let micros = (value.micros as u64) ^ (0x8000_0000_0000_0000u64);
924
925        buf[..4].copy_from_slice(&months.to_be_bytes());
926        buf[4..8].copy_from_slice(&days.to_be_bytes());
927        buf[8..].copy_from_slice(&micros.to_be_bytes());
928
929        PackedInterval(buf)
930    }
931
932    #[inline]
933    fn into_value(self) -> Interval {
934        // Note: We XOR the values to get correct sorting of negative values.
935
936        let mut months = [0; 4];
937        months.copy_from_slice(&self.0[..4]);
938        let months = u32::from_be_bytes(months) ^ 0x8000_0000u32;
939
940        let mut days = [0; 4];
941        days.copy_from_slice(&self.0[4..8]);
942        let days = u32::from_be_bytes(days) ^ 0x8000_0000u32;
943
944        let mut micros = [0; 8];
945        micros.copy_from_slice(&self.0[8..]);
946        let micros = u64::from_be_bytes(micros) ^ 0x8000_0000_0000_0000u64;
947
948        Interval {
949            months: months as i32,
950            days: days as i32,
951            micros: micros as i64,
952        }
953    }
954}
955
956#[cfg(test)]
957mod test {
958    use mz_ore::assert_ok;
959    use mz_proto::protobuf_roundtrip;
960
961    use super::*;
962    use proptest::prelude::*;
963
964    #[mz_ore::test]
965    fn interval_fmt() {
966        fn mon(mon: i32) -> String {
967            Interval {
968                months: mon,
969                ..Default::default()
970            }
971            .to_string()
972        }
973
974        assert_eq!(mon(1), "1 mon");
975        assert_eq!(mon(12), "1 year");
976        assert_eq!(mon(13), "1 year 1 mon");
977        assert_eq!(mon(24), "2 years");
978        assert_eq!(mon(25), "2 years 1 mon");
979        assert_eq!(mon(26), "2 years 2 mons");
980
981        fn dur(days: i32, micros: i64) -> String {
982            Interval::new(0, days, micros).to_string()
983        }
984        assert_eq!(&dur(2, 0), "2 days");
985        assert_eq!(&dur(2, 3 * 60 * 60 * 1_000_000), "2 days 03:00:00");
986        assert_eq!(
987            &dur(
988                2,
989                (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
990            ),
991            "2 days 03:45:06"
992        );
993        assert_eq!(
994            &dur(2, (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000)),
995            "2 days 03:45:00"
996        );
997        assert_eq!(&dur(2, 6 * 1_000_000), "2 days 00:00:06");
998        assert_eq!(
999            &dur(2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1000            "2 days 00:45:06"
1001        );
1002        assert_eq!(
1003            &dur(2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1004            "2 days 03:00:06"
1005        );
1006        assert_eq!(
1007            &dur(
1008                0,
1009                (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1010            ),
1011            "03:45:06"
1012        );
1013        assert_eq!(
1014            &dur(0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1015            "03:00:06"
1016        );
1017        assert_eq!(&dur(0, 3 * 60 * 60 * 1_000_000), "03:00:00");
1018        assert_eq!(&dur(0, (45 * 60 * 1_000_000) + (6 * 1_000_000)), "00:45:06");
1019        assert_eq!(&dur(0, 45 * 60 * 1_000_000), "00:45:00");
1020        assert_eq!(&dur(0, 6 * 1_000_000), "00:00:06");
1021
1022        assert_eq!(&dur(-2, -6 * 1_000_000), "-2 days -00:00:06");
1023        assert_eq!(
1024            &dur(-2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1025            "-2 days -00:45:06"
1026        );
1027        assert_eq!(
1028            &dur(-2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1029            "-2 days -03:00:06"
1030        );
1031        assert_eq!(
1032            &dur(
1033                0,
1034                (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1035            ),
1036            "-03:45:06"
1037        );
1038        assert_eq!(
1039            &dur(0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1040            "-03:00:06"
1041        );
1042        assert_eq!(&dur(0, -3 * 60 * 60 * 1_000_000), "-03:00:00");
1043        assert_eq!(
1044            &dur(0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1045            "-00:45:06"
1046        );
1047        assert_eq!(&dur(0, -45 * 60 * 1_000_000), "-00:45:00");
1048        assert_eq!(&dur(0, -6 * 1_000_000), "-00:00:06");
1049
1050        fn mon_dur(mon: i32, days: i32, micros: i64) -> String {
1051            Interval::new(mon, days, micros).to_string()
1052        }
1053        assert_eq!(&mon_dur(1, 2, 6 * 1_000_000), "1 mon 2 days 00:00:06");
1054        assert_eq!(
1055            &mon_dur(1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1056            "1 mon 2 days 00:45:06"
1057        );
1058        assert_eq!(
1059            &mon_dur(1, 2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1060            "1 mon 2 days 03:00:06"
1061        );
1062        assert_eq!(
1063            &mon_dur(
1064                26,
1065                0,
1066                (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1067            ),
1068            "2 years 2 mons 03:45:06"
1069        );
1070        assert_eq!(
1071            &mon_dur(26, 0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1072            "2 years 2 mons 03:00:06"
1073        );
1074        assert_eq!(
1075            &mon_dur(26, 0, 3 * 60 * 60 * 1_000_000),
1076            "2 years 2 mons 03:00:00"
1077        );
1078        assert_eq!(
1079            &mon_dur(26, 0, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1080            "2 years 2 mons 00:45:06"
1081        );
1082        assert_eq!(
1083            &mon_dur(26, 0, 45 * 60 * 1_000_000),
1084            "2 years 2 mons 00:45:00"
1085        );
1086        assert_eq!(&mon_dur(26, 0, 6 * 1_000_000), "2 years 2 mons 00:00:06");
1087
1088        assert_eq!(
1089            &mon_dur(26, -2, -6 * 1_000_000),
1090            "2 years 2 mons -2 days -00:00:06"
1091        );
1092        assert_eq!(
1093            &mon_dur(26, -2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1094            "2 years 2 mons -2 days -00:45:06"
1095        );
1096        assert_eq!(
1097            &mon_dur(26, -2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1098            "2 years 2 mons -2 days -03:00:06"
1099        );
1100        assert_eq!(
1101            &mon_dur(
1102                26,
1103                0,
1104                (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1105            ),
1106            "2 years 2 mons -03:45:06"
1107        );
1108        assert_eq!(
1109            &mon_dur(26, 0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1110            "2 years 2 mons -03:00:06"
1111        );
1112        assert_eq!(
1113            &mon_dur(26, 0, -3 * 60 * 60 * 1_000_000),
1114            "2 years 2 mons -03:00:00"
1115        );
1116        assert_eq!(
1117            &mon_dur(26, 0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1118            "2 years 2 mons -00:45:06"
1119        );
1120        assert_eq!(
1121            &mon_dur(26, 0, -45 * 60 * 1_000_000),
1122            "2 years 2 mons -00:45:00"
1123        );
1124        assert_eq!(&mon_dur(26, 0, -6 * 1_000_000), "2 years 2 mons -00:00:06");
1125
1126        assert_eq!(&mon_dur(-1, 2, 6 * 1_000_000), "-1 mons +2 days 00:00:06");
1127        assert_eq!(
1128            &mon_dur(-1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1129            "-1 mons +2 days 00:45:06"
1130        );
1131        assert_eq!(
1132            &mon_dur(-1, 2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1133            "-1 mons +2 days 03:00:06"
1134        );
1135        assert_eq!(
1136            &mon_dur(
1137                -26,
1138                0,
1139                (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1140            ),
1141            "-2 years -2 mons +03:45:06"
1142        );
1143        assert_eq!(
1144            &mon_dur(-26, 0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1145            "-2 years -2 mons +03:00:06"
1146        );
1147        assert_eq!(
1148            &mon_dur(-26, 0, 3 * 60 * 60 * 1_000_000),
1149            "-2 years -2 mons +03:00:00"
1150        );
1151        assert_eq!(
1152            &mon_dur(-26, 0, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1153            "-2 years -2 mons +00:45:06"
1154        );
1155        assert_eq!(
1156            &mon_dur(-26, 0, 45 * 60 * 1_000_000),
1157            "-2 years -2 mons +00:45:00"
1158        );
1159        assert_eq!(
1160            &mon_dur(-26, 0, 6 * 1_000_000),
1161            "-2 years -2 mons +00:00:06"
1162        );
1163
1164        assert_eq!(
1165            &mon_dur(-26, -2, -6 * 1_000_000),
1166            "-2 years -2 mons -2 days -00:00:06"
1167        );
1168        assert_eq!(
1169            &mon_dur(-26, -2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1170            "-2 years -2 mons -2 days -00:45:06"
1171        );
1172        assert_eq!(
1173            &mon_dur(-26, -2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1174            "-2 years -2 mons -2 days -03:00:06"
1175        );
1176        assert_eq!(
1177            &mon_dur(
1178                -26,
1179                0,
1180                (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1181            ),
1182            "-2 years -2 mons -03:45:06"
1183        );
1184        assert_eq!(
1185            &mon_dur(-26, 0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1186            "-2 years -2 mons -03:00:06"
1187        );
1188        assert_eq!(
1189            &mon_dur(-26, 0, -3 * 60 * 60 * 1_000_000),
1190            "-2 years -2 mons -03:00:00"
1191        );
1192        assert_eq!(
1193            &mon_dur(-26, 0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1194            "-2 years -2 mons -00:45:06"
1195        );
1196        assert_eq!(
1197            &mon_dur(-26, 0, -45 * 60 * 1_000_000),
1198            "-2 years -2 mons -00:45:00"
1199        );
1200        assert_eq!(
1201            &mon_dur(-26, 0, -6 * 1_000_000),
1202            "-2 years -2 mons -00:00:06"
1203        );
1204    }
1205
1206    #[mz_ore::test]
1207    fn test_interval_value_truncate_low_fields() {
1208        use DateTimeField::*;
1209
1210        let mut test_cases = [
1211            (
1212                Year,
1213                None,
1214                (
1215                    321,
1216                    7,
1217                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1218                ),
1219                (26 * 12, 0, 0),
1220            ),
1221            (
1222                Month,
1223                None,
1224                (
1225                    321,
1226                    7,
1227                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1228                ),
1229                (321, 0, 0),
1230            ),
1231            (
1232                Day,
1233                None,
1234                (
1235                    321,
1236                    7,
1237                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1238                ),
1239                (321, 7, 0),
1240            ),
1241            (
1242                Hour,
1243                None,
1244                (
1245                    321,
1246                    7,
1247                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1248                ),
1249                (321, 7, 13 * 60 * 60 * 1_000_000),
1250            ),
1251            (
1252                Minute,
1253                None,
1254                (
1255                    321,
1256                    7,
1257                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1258                ),
1259                (321, 7, (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000)),
1260            ),
1261            (
1262                Second,
1263                None,
1264                (
1265                    321,
1266                    7,
1267                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1268                ),
1269                (
1270                    321,
1271                    7,
1272                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1273                ),
1274            ),
1275            (
1276                Second,
1277                Some(1),
1278                (
1279                    321,
1280                    7,
1281                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1282                ),
1283                (
1284                    321,
1285                    7,
1286                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 300_000,
1287                ),
1288            ),
1289            (
1290                Second,
1291                Some(0),
1292                (
1293                    321,
1294                    7,
1295                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1296                ),
1297                (
1298                    321,
1299                    7,
1300                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1301                ),
1302            ),
1303        ];
1304
1305        for test in test_cases.iter_mut() {
1306            let mut i = Interval::new((test.2).0, (test.2).1, (test.2).2);
1307            let j = Interval::new((test.3).0, (test.3).1, (test.3).2);
1308
1309            i.truncate_low_fields(test.0, test.1, RoundBehavior::Nearest)
1310                .unwrap();
1311
1312            if i != j {
1313                panic!(
1314                    "test_interval_value_truncate_low_fields failed on {} \n actual: {:?} \n expected: {:?}",
1315                    test.0, i, j
1316                );
1317            }
1318        }
1319    }
1320
1321    #[mz_ore::test]
1322    fn test_convert_date_time_unit() {
1323        assert_eq!(
1324            Some(1_123_200_000_000),
1325            Interval::convert_date_time_unit(
1326                DateTimeField::Day,
1327                DateTimeField::Microseconds,
1328                13i64
1329            )
1330        );
1331
1332        assert_eq!(
1333            Some(3_558_399_705),
1334            Interval::convert_date_time_unit(
1335                DateTimeField::Milliseconds,
1336                DateTimeField::Month,
1337                i64::MAX
1338            )
1339        );
1340
1341        assert_eq!(
1342            None,
1343            Interval::convert_date_time_unit(
1344                DateTimeField::Minute,
1345                DateTimeField::Second,
1346                i32::MAX
1347            )
1348        );
1349
1350        assert_eq!(
1351            Some(1),
1352            Interval::convert_date_time_unit(DateTimeField::Day, DateTimeField::Year, 365)
1353        );
1354
1355        // Strange behavior due to months having 30 days
1356        assert_eq!(
1357            Some(360),
1358            Interval::convert_date_time_unit(DateTimeField::Year, DateTimeField::Day, 1)
1359        );
1360    }
1361
1362    #[mz_ore::test]
1363    fn proptest_packed_interval_roundtrips() {
1364        fn roundtrip_interval(og: Interval) {
1365            let packed = PackedInterval::from_value(og);
1366            let rnd = packed.into_value();
1367
1368            assert_eq!(og, rnd);
1369        }
1370
1371        proptest!(|(interval in any::<Interval>())| {
1372            roundtrip_interval(interval);
1373        });
1374    }
1375
1376    #[mz_ore::test]
1377    #[cfg_attr(miri, ignore)] // too slow
1378    fn proptest_packed_interval_sorts() {
1379        fn sort_intervals(mut og: Vec<Interval>) {
1380            let mut packed: Vec<_> = og.iter().copied().map(PackedInterval::from_value).collect();
1381
1382            og.sort();
1383            packed.sort();
1384
1385            let rnd: Vec<_> = packed.into_iter().map(PackedInterval::into_value).collect();
1386
1387            assert_eq!(og, rnd);
1388        }
1389
1390        proptest!(|(interval in any::<Vec<Interval>>())| {
1391            sort_intervals(interval);
1392        });
1393    }
1394
1395    // `Interval` <-> `ProtoInterval` is a field-for-field copy today, so this only
1396    // bites once the two structs drift: a field added to `Interval` but not carried
1397    // through `ProtoInterval` silently decodes as that field's default. NOTE: the
1398    // guard is blind unless `Interval::arbitrary` also generates the new field, so
1399    // extend the strategy alongside the field.
1400    proptest! {
1401        #[mz_ore::test]
1402        fn interval_protobuf_roundtrip(expect in any::<Interval>()) {
1403            let actual = protobuf_roundtrip::<_, ProtoInterval>(&expect);
1404            assert_ok!(actual);
1405            assert_eq!(actual.unwrap(), expect);
1406        }
1407    }
1408}