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