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