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