Skip to main content

mz_repr/adt/
interval.rs

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