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 "tail" of the interval, removing all time units less than `f`.
452    /// # Arguments
453    /// - `f`: Round the interval down to the specified time unit.
454    /// - `fsec_max_precision`: If `Some(x)`, keep only `x` places of microsecond precision.
455    ///    Must be `(0,6)`.
456    ///
457    /// # Errors
458    /// - If `fsec_max_precision` is not None or within (0,6).
459    pub fn truncate_low_fields(
460        &mut self,
461        f: DateTimeField,
462        fsec_max_precision: Option<u64>,
463        round_behavior: RoundBehavior,
464    ) -> Result<(), anyhow::Error> {
465        use DateTimeField::*;
466        match f {
467            Millennium => {
468                self.months -= self.months % (12 * 1000);
469                self.days = 0;
470                self.micros = 0;
471            }
472            Century => {
473                self.months -= self.months % (12 * 100);
474                self.days = 0;
475                self.micros = 0;
476            }
477            Decade => {
478                self.months -= self.months % (12 * 10);
479                self.days = 0;
480                self.micros = 0;
481            }
482            Year => {
483                self.months -= self.months % 12;
484                self.days = 0;
485                self.micros = 0;
486            }
487            Month => {
488                self.days = 0;
489                self.micros = 0;
490            }
491            // Round microseconds.
492            Second => {
493                let default_precision = 6;
494                let precision = match fsec_max_precision {
495                    Some(p) => p,
496                    None => default_precision,
497                };
498
499                if precision > default_precision {
500                    bail!(
501                        "SECOND precision must be (0, 6), have SECOND({})",
502                        precision
503                    )
504                }
505
506                let precision = match u32::try_from(precision) {
507                    Ok(p) => p,
508                    Err(_) => bail!(
509                        "SECOND precision must be (0, 6), have SECOND({})",
510                        precision
511                    ),
512                };
513                // Truncate sub-second part.
514                let remainder = self.micros % 10_i64.pow(6 - precision);
515                self.micros -= remainder;
516                // Check if value should round up/down to nearest fractional place.
517                if matches!(round_behavior, RoundBehavior::Nearest)
518                    && u64::from(precision) != default_precision
519                {
520                    let rounding_digit = remainder / 10_i64.pow(5 - precision);
521                    let micros = if rounding_digit > 4 {
522                        self.micros.checked_add(10_i64.pow(6 - precision))
523                    } else if rounding_digit < -4 {
524                        self.micros.checked_sub(10_i64.pow(6 - precision))
525                    } else {
526                        Some(self.micros)
527                    };
528                    let Some(micros) = micros else {
529                        bail!("interval field value out of range: \"{self}\"");
530                    };
531                    self.micros = micros;
532                }
533            }
534            Day => {
535                self.micros = 0;
536            }
537            Hour | Minute | Milliseconds | Microseconds => {
538                self.micros -= self.micros % f.micros_multiplier();
539            }
540        }
541        Ok(())
542    }
543
544    /// Returns a new Interval with only the time component
545    pub fn as_time_interval(&self) -> Self {
546        Self::new(0, 0, self.micros)
547    }
548
549    /// Returns true if combining all fields results in a negative number, false otherwise
550    pub fn is_negative(&self) -> bool {
551        self.as_microseconds() < 0
552    }
553
554    /// Convert val from source unit to dest unit. Does not maintain fractional values.
555    /// Returns None if the result overflows/underflows.
556    ///
557    /// WARNING: Due to the fact that Intervals consider months to have 30 days, you may get
558    /// unexpected and incorrect results when trying to convert from a non-year type to a year type
559    /// and vice versa. For example from years to days.
560    pub fn convert_date_time_unit<T>(
561        source: DateTimeField,
562        dest: DateTimeField,
563        val: T,
564    ) -> Option<T>
565    where
566        T: From<u16> + CheckedMul + std::ops::DivAssign,
567    {
568        if source < dest {
569            Self::convert_date_time_unit_increasing(source, dest, val)
570        } else if source > dest {
571            Self::convert_date_time_unit_decreasing(source, dest, val)
572        } else {
573            Some(val)
574        }
575    }
576
577    fn convert_date_time_unit_increasing<T>(
578        source: DateTimeField,
579        dest: DateTimeField,
580        val: T,
581    ) -> Option<T>
582    where
583        T: From<u16> + std::ops::DivAssign,
584    {
585        let mut cur_unit = source;
586        let mut res = val;
587        while cur_unit < dest {
588            let divisor: T = match cur_unit {
589                DateTimeField::Millennium => 1.into(),
590                DateTimeField::Century => Self::CENTURY_PER_MILLENNIUM.into(),
591                DateTimeField::Decade => Self::DECADE_PER_CENTURY.into(),
592                DateTimeField::Year => Self::YEAR_PER_DECADE.into(),
593                DateTimeField::Month => Self::MONTH_PER_YEAR.into(),
594                DateTimeField::Day => Self::DAY_PER_MONTH.into(),
595                DateTimeField::Hour => Self::HOUR_PER_DAY.into(),
596                DateTimeField::Minute => Self::MINUTE_PER_HOUR.into(),
597                DateTimeField::Second => Self::SECOND_PER_MINUTE.into(),
598                DateTimeField::Milliseconds => Self::MILLISECOND_PER_SECOND.into(),
599                DateTimeField::Microseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
600            };
601            res /= divisor;
602            cur_unit = cur_unit.next_largest();
603        }
604
605        Some(res)
606    }
607
608    fn convert_date_time_unit_decreasing<T>(
609        source: DateTimeField,
610        dest: DateTimeField,
611        val: T,
612    ) -> Option<T>
613    where
614        T: From<u16> + CheckedMul,
615    {
616        let mut cur_unit = source;
617        let mut res = val;
618        while cur_unit > dest {
619            let multiplier: T = match cur_unit {
620                DateTimeField::Millennium => Self::CENTURY_PER_MILLENNIUM.into(),
621                DateTimeField::Century => Self::DECADE_PER_CENTURY.into(),
622                DateTimeField::Decade => Self::YEAR_PER_DECADE.into(),
623                DateTimeField::Year => Self::MONTH_PER_YEAR.into(),
624                DateTimeField::Month => Self::DAY_PER_MONTH.into(),
625                DateTimeField::Day => Self::HOUR_PER_DAY.into(),
626                DateTimeField::Hour => Self::MINUTE_PER_HOUR.into(),
627                DateTimeField::Minute => Self::SECOND_PER_MINUTE.into(),
628                DateTimeField::Second => Self::MILLISECOND_PER_SECOND.into(),
629                DateTimeField::Milliseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
630                DateTimeField::Microseconds => 1.into(),
631            };
632            res = match res.checked_mul(&multiplier) {
633                Some(r) => r,
634                None => return None,
635            };
636            cur_unit = cur_unit.next_smallest();
637        }
638
639        Some(res)
640    }
641
642    /// Adjust interval so 'days' contains less than 30 days, adding the excess to 'months'.
643    pub fn justify_days(&self) -> Result<Self, anyhow::Error> {
644        let days_per_month = i32::from(Self::DAY_PER_MONTH);
645        let (mut months, mut days) = Self::justify_days_inner(self.months, self.days)?;
646        if months > 0 && days < 0 {
647            days += days_per_month;
648            months -= 1;
649        } else if months < 0 && days > 0 {
650            days -= days_per_month;
651            months += 1;
652        }
653
654        Ok(Self::new(months, days, self.micros))
655    }
656
657    fn justify_days_inner(months: i32, days: i32) -> Result<(i32, i32), anyhow::Error> {
658        let days_per_month = i32::from(Self::DAY_PER_MONTH);
659        let whole_month = days / days_per_month;
660        let days = days - whole_month * days_per_month;
661
662        let months = months
663            .checked_add(whole_month)
664            .ok_or_else(|| anyhow!(&*MONTH_OVERFLOW_ERROR))?;
665
666        Ok((months, days))
667    }
668
669    /// Adjust interval so 'micros' contains less than a whole day, adding the excess to 'days'.
670    pub fn justify_hours(&self) -> Result<Self, anyhow::Error> {
671        let (mut days, mut micros) = Self::justify_hours_inner(self.days, self.micros)?;
672        if days > 0 && micros < 0 {
673            micros += &*USECS_PER_DAY;
674            days -= 1;
675        } else if days < 0 && micros > 0 {
676            micros -= &*USECS_PER_DAY;
677            days += 1;
678        }
679
680        Ok(Self::new(self.months, days, micros))
681    }
682
683    fn justify_hours_inner(days: i32, micros: i64) -> Result<(i32, i64), anyhow::Error> {
684        let days = i32::try_from(micros / &*USECS_PER_DAY)
685            .ok()
686            .and_then(|d| days.checked_add(d))
687            .ok_or_else(|| anyhow!(&*DAY_OVERFLOW_ERROR))?;
688        let micros = micros % &*USECS_PER_DAY;
689
690        Ok((days, micros))
691    }
692
693    /// Adjust interval so 'days' contains less than 30 days, adding the excess to 'months'.
694    /// Adjust interval so 'micros' contains less than a whole day, adding the excess to 'days'.
695    /// Also, the sign bit on all three fields is made equal, so either all three fields are negative or all are positive.
696    pub fn justify_interval(&self) -> Result<Self, anyhow::Error> {
697        let days_per_month = i32::from(Self::DAY_PER_MONTH);
698        let mut months = self.months;
699        let mut days = self.days;
700        let micros = self.micros;
701        // We justify days twice to try to avoid an intermediate overflow of days if it would be
702        // able to fit in months.
703        if (days > 0 && micros > 0) || (days < 0 && micros < 0) {
704            let (m, d) = Self::justify_days_inner(self.months, self.days)?;
705            months = m;
706            days = d;
707        }
708        let (days, mut micros) = Self::justify_hours_inner(days, micros)?;
709        let (mut months, mut days) = Self::justify_days_inner(months, days)?;
710
711        if months > 0 && (days < 0 || (days == 0 && micros < 0)) {
712            days += days_per_month;
713            months -= 1;
714        } else if months < 0 && (days > 0 || (days == 0 && micros > 0)) {
715            days -= days_per_month;
716            months += 1;
717        }
718
719        if days > 0 && micros < 0 {
720            micros += &*USECS_PER_DAY;
721            days -= 1;
722        } else if days < 0 && micros > 0 {
723            micros -= &*USECS_PER_DAY;
724            days += 1;
725        }
726
727        Ok(Self::new(months, days, micros))
728    }
729}
730
731/// Format an interval in a human form
732///
733/// Example outputs:
734///
735/// * 1 year 2 mons 5 days 03:04:00
736/// * -1 year +5 days +18:59:29.3
737/// * 00:00:00
738impl fmt::Display for Interval {
739    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
740        let neg_months = self.months < 0;
741        let years = (self.months / 12).abs();
742        let months = (self.months % 12).abs();
743
744        let neg_days = self.days < 0;
745        let days = i64::from(self.days).abs();
746
747        let mut nanos = self.nanoseconds().abs();
748        let mut secs = (self.micros / 1_000_000).abs();
749
750        let sec_per_hr = 60 * 60;
751        let hours = secs / sec_per_hr;
752        secs %= sec_per_hr;
753
754        let sec_per_min = 60;
755        let minutes = secs / sec_per_min;
756        secs %= sec_per_min;
757
758        if years > 0 {
759            if neg_months {
760                f.write_char('-')?;
761            }
762            write!(f, "{} year", years)?;
763            if years > 1 || neg_months {
764                f.write_char('s')?;
765            }
766        }
767
768        if months > 0 {
769            if years != 0 {
770                f.write_char(' ')?;
771            }
772            if neg_months {
773                f.write_char('-')?;
774            }
775            write!(f, "{} mon", months)?;
776            if months > 1 || neg_months {
777                f.write_char('s')?;
778            }
779        }
780
781        if days != 0 {
782            if years > 0 || months > 0 {
783                f.write_char(' ')?;
784            }
785            if neg_months && !neg_days {
786                f.write_char('+')?;
787            }
788            write!(f, "{} day", self.days)?;
789            if self.days != 1 {
790                f.write_char('s')?;
791            }
792        }
793
794        let non_zero_hmsn = hours > 0 || minutes > 0 || secs > 0 || nanos > 0;
795
796        if (years == 0 && months == 0 && days == 0) || non_zero_hmsn {
797            if years > 0 || months > 0 || days > 0 {
798                f.write_char(' ')?;
799            }
800            if self.micros < 0 && non_zero_hmsn {
801                f.write_char('-')?;
802            } else if neg_days || (days == 0 && neg_months) {
803                f.write_char('+')?;
804            }
805            write!(f, "{:02}:{:02}:{:02}", hours, minutes, secs)?;
806            if nanos > 0 {
807                let mut width = 9;
808                while nanos % 10 == 0 {
809                    width -= 1;
810                    nanos /= 10;
811                }
812                write!(f, ".{:0width$}", nanos, width = width)?;
813            }
814        }
815
816        Ok(())
817    }
818}
819
820#[cfg(any(test, feature = "proptest"))]
821impl Arbitrary for Interval {
822    type Strategy = BoxedStrategy<Self>;
823    type Parameters = ();
824
825    fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
826        (
827            any::<i32>(),
828            any::<i32>(),
829            ((((i64::from(i32::MIN) * 60) - 59) * 60) * 1_000_000 - 59_999_999
830                ..(((i64::from(i32::MAX) * 60) + 59) * 60) * 1_000_000 + 59_999_999),
831        )
832            .prop_map(|(months, days, micros)| Interval {
833                months,
834                days,
835                micros,
836            })
837            .boxed()
838    }
839}
840
841/// An encoded packed variant of [`Interval`].
842///
843/// We uphold the variant that [`PackedInterval`] sorts the same as [`Interval`].
844#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
845pub struct PackedInterval([u8; Self::SIZE]);
846
847// `as` conversions are okay here because we're doing bit level logic to make
848// sure the sort order of the packed binary is correct. This is implementation
849// is proptest-ed below.
850#[allow(clippy::as_conversions)]
851impl FixedSizeCodec<Interval> for PackedInterval {
852    const SIZE: usize = 16;
853
854    fn as_bytes(&self) -> &[u8] {
855        &self.0[..]
856    }
857
858    fn from_bytes(slice: &[u8]) -> Result<Self, String> {
859        let buf: [u8; Self::SIZE] = slice.try_into().map_err(|_| {
860            format!(
861                "size for PackedInterval is {} bytes, got {}",
862                Self::SIZE,
863                slice.len()
864            )
865        })?;
866        Ok(PackedInterval(buf))
867    }
868
869    #[inline]
870    fn from_value(value: Interval) -> Self {
871        let mut buf = [0u8; 16];
872
873        // Note: We XOR the values to get correct sorting of negative values.
874
875        let months = (value.months as u32) ^ (0x8000_0000u32);
876        let days = (value.days as u32) ^ (0x8000_0000u32);
877        let micros = (value.micros as u64) ^ (0x8000_0000_0000_0000u64);
878
879        buf[..4].copy_from_slice(&months.to_be_bytes());
880        buf[4..8].copy_from_slice(&days.to_be_bytes());
881        buf[8..].copy_from_slice(&micros.to_be_bytes());
882
883        PackedInterval(buf)
884    }
885
886    #[inline]
887    fn into_value(self) -> Interval {
888        // Note: We XOR the values to get correct sorting of negative values.
889
890        let mut months = [0; 4];
891        months.copy_from_slice(&self.0[..4]);
892        let months = u32::from_be_bytes(months) ^ 0x8000_0000u32;
893
894        let mut days = [0; 4];
895        days.copy_from_slice(&self.0[4..8]);
896        let days = u32::from_be_bytes(days) ^ 0x8000_0000u32;
897
898        let mut micros = [0; 8];
899        micros.copy_from_slice(&self.0[8..]);
900        let micros = u64::from_be_bytes(micros) ^ 0x8000_0000_0000_0000u64;
901
902        Interval {
903            months: months as i32,
904            days: days as i32,
905            micros: micros as i64,
906        }
907    }
908}
909
910#[cfg(test)]
911mod test {
912    use super::*;
913    use proptest::prelude::*;
914
915    #[mz_ore::test]
916    fn interval_fmt() {
917        fn mon(mon: i32) -> String {
918            Interval {
919                months: mon,
920                ..Default::default()
921            }
922            .to_string()
923        }
924
925        assert_eq!(mon(1), "1 mon");
926        assert_eq!(mon(12), "1 year");
927        assert_eq!(mon(13), "1 year 1 mon");
928        assert_eq!(mon(24), "2 years");
929        assert_eq!(mon(25), "2 years 1 mon");
930        assert_eq!(mon(26), "2 years 2 mons");
931
932        fn dur(days: i32, micros: i64) -> String {
933            Interval::new(0, days, micros).to_string()
934        }
935        assert_eq!(&dur(2, 0), "2 days");
936        assert_eq!(&dur(2, 3 * 60 * 60 * 1_000_000), "2 days 03:00:00");
937        assert_eq!(
938            &dur(
939                2,
940                (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
941            ),
942            "2 days 03:45:06"
943        );
944        assert_eq!(
945            &dur(2, (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000)),
946            "2 days 03:45:00"
947        );
948        assert_eq!(&dur(2, 6 * 1_000_000), "2 days 00:00:06");
949        assert_eq!(
950            &dur(2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
951            "2 days 00:45:06"
952        );
953        assert_eq!(
954            &dur(2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
955            "2 days 03:00:06"
956        );
957        assert_eq!(
958            &dur(
959                0,
960                (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
961            ),
962            "03:45:06"
963        );
964        assert_eq!(
965            &dur(0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
966            "03:00:06"
967        );
968        assert_eq!(&dur(0, 3 * 60 * 60 * 1_000_000), "03:00:00");
969        assert_eq!(&dur(0, (45 * 60 * 1_000_000) + (6 * 1_000_000)), "00:45:06");
970        assert_eq!(&dur(0, 45 * 60 * 1_000_000), "00:45:00");
971        assert_eq!(&dur(0, 6 * 1_000_000), "00:00:06");
972
973        assert_eq!(&dur(-2, -6 * 1_000_000), "-2 days -00:00:06");
974        assert_eq!(
975            &dur(-2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
976            "-2 days -00:45:06"
977        );
978        assert_eq!(
979            &dur(-2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
980            "-2 days -03:00:06"
981        );
982        assert_eq!(
983            &dur(
984                0,
985                (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
986            ),
987            "-03:45:06"
988        );
989        assert_eq!(
990            &dur(0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
991            "-03:00:06"
992        );
993        assert_eq!(&dur(0, -3 * 60 * 60 * 1_000_000), "-03:00:00");
994        assert_eq!(
995            &dur(0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
996            "-00:45:06"
997        );
998        assert_eq!(&dur(0, -45 * 60 * 1_000_000), "-00:45:00");
999        assert_eq!(&dur(0, -6 * 1_000_000), "-00:00:06");
1000
1001        fn mon_dur(mon: i32, days: i32, micros: i64) -> String {
1002            Interval::new(mon, days, micros).to_string()
1003        }
1004        assert_eq!(&mon_dur(1, 2, 6 * 1_000_000), "1 mon 2 days 00:00:06");
1005        assert_eq!(
1006            &mon_dur(1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1007            "1 mon 2 days 00:45:06"
1008        );
1009        assert_eq!(
1010            &mon_dur(1, 2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1011            "1 mon 2 days 03:00:06"
1012        );
1013        assert_eq!(
1014            &mon_dur(
1015                26,
1016                0,
1017                (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1018            ),
1019            "2 years 2 mons 03:45:06"
1020        );
1021        assert_eq!(
1022            &mon_dur(26, 0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1023            "2 years 2 mons 03:00:06"
1024        );
1025        assert_eq!(
1026            &mon_dur(26, 0, 3 * 60 * 60 * 1_000_000),
1027            "2 years 2 mons 03:00:00"
1028        );
1029        assert_eq!(
1030            &mon_dur(26, 0, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1031            "2 years 2 mons 00:45:06"
1032        );
1033        assert_eq!(
1034            &mon_dur(26, 0, 45 * 60 * 1_000_000),
1035            "2 years 2 mons 00:45:00"
1036        );
1037        assert_eq!(&mon_dur(26, 0, 6 * 1_000_000), "2 years 2 mons 00:00:06");
1038
1039        assert_eq!(
1040            &mon_dur(26, -2, -6 * 1_000_000),
1041            "2 years 2 mons -2 days -00:00:06"
1042        );
1043        assert_eq!(
1044            &mon_dur(26, -2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1045            "2 years 2 mons -2 days -00:45:06"
1046        );
1047        assert_eq!(
1048            &mon_dur(26, -2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1049            "2 years 2 mons -2 days -03:00:06"
1050        );
1051        assert_eq!(
1052            &mon_dur(
1053                26,
1054                0,
1055                (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1056            ),
1057            "2 years 2 mons -03:45:06"
1058        );
1059        assert_eq!(
1060            &mon_dur(26, 0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1061            "2 years 2 mons -03:00:06"
1062        );
1063        assert_eq!(
1064            &mon_dur(26, 0, -3 * 60 * 60 * 1_000_000),
1065            "2 years 2 mons -03:00:00"
1066        );
1067        assert_eq!(
1068            &mon_dur(26, 0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1069            "2 years 2 mons -00:45:06"
1070        );
1071        assert_eq!(
1072            &mon_dur(26, 0, -45 * 60 * 1_000_000),
1073            "2 years 2 mons -00:45:00"
1074        );
1075        assert_eq!(&mon_dur(26, 0, -6 * 1_000_000), "2 years 2 mons -00:00:06");
1076
1077        assert_eq!(&mon_dur(-1, 2, 6 * 1_000_000), "-1 mons +2 days 00:00:06");
1078        assert_eq!(
1079            &mon_dur(-1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1080            "-1 mons +2 days 00:45:06"
1081        );
1082        assert_eq!(
1083            &mon_dur(-1, 2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1084            "-1 mons +2 days 03:00:06"
1085        );
1086        assert_eq!(
1087            &mon_dur(
1088                -26,
1089                0,
1090                (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1091            ),
1092            "-2 years -2 mons +03:45:06"
1093        );
1094        assert_eq!(
1095            &mon_dur(-26, 0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1096            "-2 years -2 mons +03:00:06"
1097        );
1098        assert_eq!(
1099            &mon_dur(-26, 0, 3 * 60 * 60 * 1_000_000),
1100            "-2 years -2 mons +03:00:00"
1101        );
1102        assert_eq!(
1103            &mon_dur(-26, 0, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1104            "-2 years -2 mons +00:45:06"
1105        );
1106        assert_eq!(
1107            &mon_dur(-26, 0, 45 * 60 * 1_000_000),
1108            "-2 years -2 mons +00:45:00"
1109        );
1110        assert_eq!(
1111            &mon_dur(-26, 0, 6 * 1_000_000),
1112            "-2 years -2 mons +00:00:06"
1113        );
1114
1115        assert_eq!(
1116            &mon_dur(-26, -2, -6 * 1_000_000),
1117            "-2 years -2 mons -2 days -00:00:06"
1118        );
1119        assert_eq!(
1120            &mon_dur(-26, -2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1121            "-2 years -2 mons -2 days -00:45:06"
1122        );
1123        assert_eq!(
1124            &mon_dur(-26, -2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1125            "-2 years -2 mons -2 days -03:00:06"
1126        );
1127        assert_eq!(
1128            &mon_dur(
1129                -26,
1130                0,
1131                (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1132            ),
1133            "-2 years -2 mons -03:45:06"
1134        );
1135        assert_eq!(
1136            &mon_dur(-26, 0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1137            "-2 years -2 mons -03:00:06"
1138        );
1139        assert_eq!(
1140            &mon_dur(-26, 0, -3 * 60 * 60 * 1_000_000),
1141            "-2 years -2 mons -03:00:00"
1142        );
1143        assert_eq!(
1144            &mon_dur(-26, 0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1145            "-2 years -2 mons -00:45:06"
1146        );
1147        assert_eq!(
1148            &mon_dur(-26, 0, -45 * 60 * 1_000_000),
1149            "-2 years -2 mons -00:45:00"
1150        );
1151        assert_eq!(
1152            &mon_dur(-26, 0, -6 * 1_000_000),
1153            "-2 years -2 mons -00:00:06"
1154        );
1155    }
1156
1157    #[mz_ore::test]
1158    fn test_interval_value_truncate_low_fields() {
1159        use DateTimeField::*;
1160
1161        let mut test_cases = [
1162            (
1163                Year,
1164                None,
1165                (
1166                    321,
1167                    7,
1168                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1169                ),
1170                (26 * 12, 0, 0),
1171            ),
1172            (
1173                Month,
1174                None,
1175                (
1176                    321,
1177                    7,
1178                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1179                ),
1180                (321, 0, 0),
1181            ),
1182            (
1183                Day,
1184                None,
1185                (
1186                    321,
1187                    7,
1188                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1189                ),
1190                (321, 7, 0),
1191            ),
1192            (
1193                Hour,
1194                None,
1195                (
1196                    321,
1197                    7,
1198                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1199                ),
1200                (321, 7, 13 * 60 * 60 * 1_000_000),
1201            ),
1202            (
1203                Minute,
1204                None,
1205                (
1206                    321,
1207                    7,
1208                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1209                ),
1210                (321, 7, (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000)),
1211            ),
1212            (
1213                Second,
1214                None,
1215                (
1216                    321,
1217                    7,
1218                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1219                ),
1220                (
1221                    321,
1222                    7,
1223                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1224                ),
1225            ),
1226            (
1227                Second,
1228                Some(1),
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                (
1235                    321,
1236                    7,
1237                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 300_000,
1238                ),
1239            ),
1240            (
1241                Second,
1242                Some(0),
1243                (
1244                    321,
1245                    7,
1246                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1247                ),
1248                (
1249                    321,
1250                    7,
1251                    (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1252                ),
1253            ),
1254        ];
1255
1256        for test in test_cases.iter_mut() {
1257            let mut i = Interval::new((test.2).0, (test.2).1, (test.2).2);
1258            let j = Interval::new((test.3).0, (test.3).1, (test.3).2);
1259
1260            i.truncate_low_fields(test.0, test.1, RoundBehavior::Nearest)
1261                .unwrap();
1262
1263            if i != j {
1264                panic!(
1265                    "test_interval_value_truncate_low_fields failed on {} \n actual: {:?} \n expected: {:?}",
1266                    test.0, i, j
1267                );
1268            }
1269        }
1270    }
1271
1272    #[mz_ore::test]
1273    fn test_convert_date_time_unit() {
1274        assert_eq!(
1275            Some(1_123_200_000_000),
1276            Interval::convert_date_time_unit(
1277                DateTimeField::Day,
1278                DateTimeField::Microseconds,
1279                13i64
1280            )
1281        );
1282
1283        assert_eq!(
1284            Some(3_558_399_705),
1285            Interval::convert_date_time_unit(
1286                DateTimeField::Milliseconds,
1287                DateTimeField::Month,
1288                i64::MAX
1289            )
1290        );
1291
1292        assert_eq!(
1293            None,
1294            Interval::convert_date_time_unit(
1295                DateTimeField::Minute,
1296                DateTimeField::Second,
1297                i32::MAX
1298            )
1299        );
1300
1301        assert_eq!(
1302            Some(1),
1303            Interval::convert_date_time_unit(DateTimeField::Day, DateTimeField::Year, 365)
1304        );
1305
1306        // Strange behavior due to months having 30 days
1307        assert_eq!(
1308            Some(360),
1309            Interval::convert_date_time_unit(DateTimeField::Year, DateTimeField::Day, 1)
1310        );
1311    }
1312
1313    #[mz_ore::test]
1314    fn proptest_packed_interval_roundtrips() {
1315        fn roundtrip_interval(og: Interval) {
1316            let packed = PackedInterval::from_value(og);
1317            let rnd = packed.into_value();
1318
1319            assert_eq!(og, rnd);
1320        }
1321
1322        proptest!(|(interval in any::<Interval>())| {
1323            roundtrip_interval(interval);
1324        });
1325    }
1326
1327    #[mz_ore::test]
1328    fn proptest_packed_interval_sorts() {
1329        fn sort_intervals(mut og: Vec<Interval>) {
1330            let mut packed: Vec<_> = og.iter().copied().map(PackedInterval::from_value).collect();
1331
1332            og.sort();
1333            packed.sort();
1334
1335            let rnd: Vec<_> = packed.into_iter().map(PackedInterval::into_value).collect();
1336
1337            assert_eq!(og, rnd);
1338        }
1339
1340        proptest!(|(interval in any::<Vec<Interval>>())| {
1341            sort_intervals(interval);
1342        });
1343    }
1344}