1use 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};
26use crate::strconv;
27
28include!(concat!(env!("OUT_DIR"), "/mz_repr.adt.interval.rs"));
29
30#[derive(
34 Debug,
35 Clone,
36 Copy,
37 PartialEq,
38 Eq,
39 PartialOrd,
40 Ord,
41 Serialize,
42 Hash,
43 Deserialize
44)]
45pub struct Interval {
46 pub months: i32,
48 pub days: i32,
52 pub micros: i64,
57}
58
59impl Default for Interval {
60 fn default() -> Self {
61 Self {
62 months: 0,
63 days: 0,
64 micros: 0,
65 }
66 }
67}
68
69impl RustType<ProtoInterval> for Interval {
70 fn into_proto(&self) -> ProtoInterval {
71 ProtoInterval {
72 months: self.months,
73 days: self.days,
74 micros: self.micros,
75 }
76 }
77
78 fn from_proto(proto: ProtoInterval) -> Result<Self, TryFromProtoError> {
79 Ok(Interval {
80 months: proto.months,
81 days: proto.days,
82 micros: proto.micros,
83 })
84 }
85}
86
87impl num_traits::ops::checked::CheckedNeg for Interval {
88 fn checked_neg(&self) -> Option<Self> {
89 if let (Some(months), Some(days), Some(micros)) = (
90 self.months.checked_neg(),
91 self.days.checked_neg(),
92 self.micros.checked_neg(),
93 ) {
94 Some(Self::new(months, days, micros))
95 } else {
96 None
97 }
98 }
99}
100
101impl std::str::FromStr for Interval {
102 type Err = anyhow::Error;
103
104 fn from_str(s: &str) -> Result<Self, Self::Err> {
105 crate::strconv::parse_interval(s).map_err(|e| anyhow!(e))
106 }
107}
108
109static MONTH_OVERFLOW_ERROR: LazyLock<String> = LazyLock::new(|| {
110 format!(
111 "Overflows maximum months; cannot exceed {}/{} microseconds",
112 i32::MAX,
113 i32::MIN,
114 )
115});
116static DAY_OVERFLOW_ERROR: LazyLock<String> = LazyLock::new(|| {
117 format!(
118 "Overflows maximum days; cannot exceed {}/{} microseconds",
119 i32::MAX,
120 i32::MIN,
121 )
122});
123pub static USECS_PER_DAY: LazyLock<i64> = LazyLock::new(|| {
124 Interval::convert_date_time_unit(DateTimeField::Day, DateTimeField::Microseconds, 1i64).unwrap()
125});
126
127#[derive(Debug, Clone)]
128pub enum RoundBehavior {
129 Truncate,
130 Nearest,
131}
132
133impl Interval {
134 pub const CENTURY_PER_MILLENNIUM: u16 = 10;
135 pub const DECADE_PER_CENTURY: u16 = 10;
136 pub const YEAR_PER_DECADE: u16 = 10;
137 pub const MONTH_PER_YEAR: u16 = 12;
138 pub const DAY_PER_MONTH: u16 = 30;
140 pub const HOUR_PER_DAY: u16 = 24;
142 pub const MINUTE_PER_HOUR: u16 = 60;
143 pub const SECOND_PER_MINUTE: u16 = 60;
144 pub const MILLISECOND_PER_SECOND: u16 = 1_000;
145 pub const MICROSECOND_PER_MILLISECOND: u16 = 1_000;
146 pub const NANOSECOND_PER_MICROSECOND: u16 = 1_000;
147 pub const EPOCH_DAYS_PER_YEAR: f64 = 365.25;
153
154 pub const fn new(months: i32, days: i32, micros: i64) -> Interval {
156 Interval {
157 months,
158 days,
159 micros,
160 }
161 }
162
163 pub fn try_new(months: i128, days: i128, micros: i128) -> Option<Interval> {
169 Some(Interval::new(
170 i32::try_from(months).ok()?,
171 i32::try_from(days).ok()?,
172 i64::try_from(micros).ok()?,
173 ))
174 }
175
176 pub fn from_duration(duration: &Duration) -> Result<Interval, anyhow::Error> {
181 if duration.subsec_nanos() % 1000 != 0 {
182 return Err(anyhow!(
183 "cannot convert Duration to Interval due to fractional microseconds"
184 ));
185 }
186 Ok(Interval {
187 months: 0,
188 days: 0,
189 micros: duration.as_micros().try_into()?,
190 })
191 }
192
193 pub fn from_literal(
199 value: &str,
200 precision_high: DateTimeField,
201 precision_low: DateTimeField,
202 fsec_max_precision: Option<u64>,
203 ) -> Result<Interval, anyhow::Error> {
204 let mut interval = strconv::parse_interval_w_disambiguator(
205 value,
206 match precision_high {
207 DateTimeField::Hour | DateTimeField::Minute => Some(precision_high),
208 _ => None,
209 },
210 precision_low,
211 )?;
212 interval.truncate_low_fields(precision_low, fsec_max_precision, RoundBehavior::Nearest)?;
216 Ok(interval)
217 }
218
219 pub fn from_chrono_duration(duration: chrono::Duration) -> Result<Self, anyhow::Error> {
222 let Some(micros) = duration.num_microseconds() else {
223 bail!("cannot convert Duration to Interval due to overflowed microseconds");
224 };
225 Ok(Self {
226 months: 0,
227 days: 0,
228 micros,
229 })
230 }
231
232 pub fn checked_add(&self, other: &Self) -> Option<Self> {
233 let months = match self.months.checked_add(other.months) {
234 Some(m) => m,
235 None => return None,
236 };
237 let days = match self.days.checked_add(other.days) {
238 Some(d) => d,
239 None => return None,
240 };
241 let micros = match self.micros.checked_add(other.micros) {
242 Some(us) => us,
243 None => return None,
244 };
245
246 Some(Self::new(months, days, micros))
247 }
248
249 pub fn checked_mul(&self, other: f64) -> Option<Self> {
250 self.checked_op(other, |f1, f2| f1 * f2)
251 }
252
253 pub fn checked_div(&self, other: f64) -> Option<Self> {
254 self.checked_op(other, |f1, f2| f1 / f2)
255 }
256
257 #[allow(clippy::as_conversions)]
260 fn checked_op<F1>(&self, other: f64, op: F1) -> Option<Self>
261 where
262 F1: Fn(f64, f64) -> f64,
263 {
264 let months = op(f64::from(self.months), other);
265 if months.is_nan()
266 || months.is_infinite()
267 || months < i32::MIN.into()
268 || months > i32::MAX.into()
269 {
270 return None;
271 }
272
273 let days =
274 op(f64::from(self.days), other) + months.fract() * f64::from(Self::DAY_PER_MONTH);
275 if days.is_nan() || days.is_infinite() || days < i32::MIN.into() || days > i32::MAX.into() {
276 return None;
277 }
278
279 let micros = op(self.micros as f64, other)
280 + days.fract()
281 * f64::from(Self::HOUR_PER_DAY)
282 * f64::from(Self::MINUTE_PER_HOUR)
283 * f64::from(Self::SECOND_PER_MINUTE)
284 * f64::from(Self::MILLISECOND_PER_SECOND)
285 * f64::from(Self::MICROSECOND_PER_MILLISECOND);
286
287 if micros.is_nan()
288 || micros.is_infinite()
289 || Numeric::from(micros) < Numeric::from(i64::MIN)
290 || Numeric::from(micros) > Numeric::from(i64::MAX)
291 {
292 return None;
293 }
294
295 Some(Self::new(months as i32, days as i32, micros as i64))
296 }
297
298 pub fn millennia(&self) -> i32 {
303 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Millennium, self.months)
304 .unwrap()
305 }
306
307 pub fn centuries(&self) -> i32 {
312 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Century, self.months)
313 .unwrap()
314 }
315
316 pub fn decades(&self) -> i32 {
321 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Decade, self.months)
322 .unwrap()
323 }
324
325 pub fn years(&self) -> i32 {
330 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Year, self.months)
331 .unwrap()
332 }
333
334 pub fn quarters(&self) -> i32 {
340 self.months() / 3 + 1
341 }
342
343 pub fn months(&self) -> i32 {
349 self.months % i32::from(Self::MONTH_PER_YEAR)
350 }
351
352 pub fn days(&self) -> i64 {
358 self.days.into()
359 }
360
361 pub fn hours(&self) -> i64 {
367 Self::convert_date_time_unit(
368 DateTimeField::Microseconds,
369 DateTimeField::Hour,
370 self.micros,
371 )
372 .unwrap()
373 % i64::from(Self::HOUR_PER_DAY)
374 }
375
376 pub fn minutes(&self) -> i64 {
382 Self::convert_date_time_unit(
383 DateTimeField::Microseconds,
384 DateTimeField::Minute,
385 self.micros,
386 )
387 .unwrap()
388 % i64::from(Self::MINUTE_PER_HOUR)
389 }
390
391 pub fn seconds<T>(&self) -> T
396 where
397 T: DecimalLike,
398 {
399 T::lossy_from(self.micros % 60_000_000) / T::from(1e6)
400 }
401
402 pub fn milliseconds<T>(&self) -> T
407 where
408 T: DecimalLike,
409 {
410 T::lossy_from(self.micros % 60_000_000) / T::from(1e3)
411 }
412
413 pub fn microseconds<T>(&self) -> T
418 where
419 T: DecimalLike,
420 {
421 T::lossy_from(self.micros % 60_000_000)
422 }
423
424 pub fn nanoseconds(&self) -> i32 {
426 (self.micros % 1_000_000 * 1_000).try_into().unwrap()
427 }
428
429 pub fn as_epoch_seconds<T>(&self) -> T
433 where
434 T: DecimalLike,
435 {
436 let days = T::from(self.years()) * T::from(Self::EPOCH_DAYS_PER_YEAR)
437 + T::from(self.months()) * T::from(Self::DAY_PER_MONTH)
438 + T::from(self.days);
439 let seconds = days
440 * T::from(Self::HOUR_PER_DAY)
441 * T::from(Self::MINUTE_PER_HOUR)
442 * T::from(Self::SECOND_PER_MINUTE);
443
444 seconds
445 + T::lossy_from(self.micros)
446 / (T::from(Self::MICROSECOND_PER_MILLISECOND)
447 * T::from(Self::MILLISECOND_PER_SECOND))
448 }
449
450 pub fn as_microseconds(&self) -> i128 {
452 Self::convert_date_time_unit(
455 DateTimeField::Month,
456 DateTimeField::Microseconds,
457 i128::from(self.months),
458 ).unwrap() +
459 Self::convert_date_time_unit(
462 DateTimeField::Day,
463 DateTimeField::Microseconds,
464 i128::from(self.days),
465 ).unwrap() +
466 i128::from(self.micros)
467 }
468
469 pub fn as_milliseconds(&self) -> i128 {
471 self.as_microseconds() / 1000
472 }
473
474 pub fn duration_as_chrono(&self) -> chrono::Duration {
476 use chrono::Duration;
477 Duration::try_days(self.days.into()).unwrap() + Duration::microseconds(self.micros)
478 }
479
480 pub fn duration(&self) -> Result<Duration, anyhow::Error> {
481 if self.months != 0 {
482 bail!("cannot convert interval with months to duration");
483 }
484 if self.is_negative() {
485 bail!("cannot convert negative interval to duration");
486 }
487 let Ok(micros) = u64::try_from(self.as_microseconds()) else {
492 bail!("interval is too large to convert to duration");
493 };
494 Ok(Duration::from_micros(micros))
495 }
496
497 pub fn truncate_low_fields(
506 &mut self,
507 f: DateTimeField,
508 fsec_max_precision: Option<u64>,
509 round_behavior: RoundBehavior,
510 ) -> Result<(), anyhow::Error> {
511 use DateTimeField::*;
512 match f {
513 Millennium => {
514 self.months -= self.months % (12 * 1000);
515 self.days = 0;
516 self.micros = 0;
517 }
518 Century => {
519 self.months -= self.months % (12 * 100);
520 self.days = 0;
521 self.micros = 0;
522 }
523 Decade => {
524 self.months -= self.months % (12 * 10);
525 self.days = 0;
526 self.micros = 0;
527 }
528 Year => {
529 self.months -= self.months % 12;
530 self.days = 0;
531 self.micros = 0;
532 }
533 Month => {
534 self.days = 0;
535 self.micros = 0;
536 }
537 Second => {
539 let default_precision = 6;
540 let precision = match fsec_max_precision {
541 Some(p) => p,
542 None => default_precision,
543 };
544
545 if precision > default_precision {
546 bail!(
547 "SECOND precision must be (0, 6), have SECOND({})",
548 precision
549 )
550 }
551
552 let precision = match u32::try_from(precision) {
553 Ok(p) => p,
554 Err(_) => bail!(
555 "SECOND precision must be (0, 6), have SECOND({})",
556 precision
557 ),
558 };
559 let remainder = self.micros % 10_i64.pow(6 - precision);
561 self.micros -= remainder;
562 if matches!(round_behavior, RoundBehavior::Nearest)
564 && u64::from(precision) != default_precision
565 {
566 let rounding_digit = remainder / 10_i64.pow(5 - precision);
567 let micros = if rounding_digit > 4 {
568 self.micros.checked_add(10_i64.pow(6 - precision))
569 } else if rounding_digit < -4 {
570 self.micros.checked_sub(10_i64.pow(6 - precision))
571 } else {
572 Some(self.micros)
573 };
574 let Some(micros) = micros else {
575 bail!("interval field value out of range: \"{self}\"");
576 };
577 self.micros = micros;
578 }
579 }
580 Day => {
581 self.micros = 0;
582 }
583 Hour | Minute | Milliseconds | Microseconds => {
584 self.micros -= self.micros % f.micros_multiplier();
585 }
586 }
587 Ok(())
588 }
589
590 pub fn as_time_interval(&self) -> Self {
592 Self::new(0, 0, self.micros)
593 }
594
595 pub fn is_negative(&self) -> bool {
597 self.as_microseconds() < 0
598 }
599
600 pub fn convert_date_time_unit<T>(
607 source: DateTimeField,
608 dest: DateTimeField,
609 val: T,
610 ) -> Option<T>
611 where
612 T: From<u16> + CheckedMul + std::ops::DivAssign,
613 {
614 if source < dest {
615 Self::convert_date_time_unit_increasing(source, dest, val)
616 } else if source > dest {
617 Self::convert_date_time_unit_decreasing(source, dest, val)
618 } else {
619 Some(val)
620 }
621 }
622
623 fn convert_date_time_unit_increasing<T>(
624 source: DateTimeField,
625 dest: DateTimeField,
626 val: T,
627 ) -> Option<T>
628 where
629 T: From<u16> + std::ops::DivAssign,
630 {
631 let mut cur_unit = source;
632 let mut res = val;
633 while cur_unit < dest {
634 let divisor: T = match cur_unit {
635 DateTimeField::Millennium => 1.into(),
636 DateTimeField::Century => Self::CENTURY_PER_MILLENNIUM.into(),
637 DateTimeField::Decade => Self::DECADE_PER_CENTURY.into(),
638 DateTimeField::Year => Self::YEAR_PER_DECADE.into(),
639 DateTimeField::Month => Self::MONTH_PER_YEAR.into(),
640 DateTimeField::Day => Self::DAY_PER_MONTH.into(),
641 DateTimeField::Hour => Self::HOUR_PER_DAY.into(),
642 DateTimeField::Minute => Self::MINUTE_PER_HOUR.into(),
643 DateTimeField::Second => Self::SECOND_PER_MINUTE.into(),
644 DateTimeField::Milliseconds => Self::MILLISECOND_PER_SECOND.into(),
645 DateTimeField::Microseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
646 };
647 res /= divisor;
648 cur_unit = cur_unit.next_largest();
649 }
650
651 Some(res)
652 }
653
654 fn convert_date_time_unit_decreasing<T>(
655 source: DateTimeField,
656 dest: DateTimeField,
657 val: T,
658 ) -> Option<T>
659 where
660 T: From<u16> + CheckedMul,
661 {
662 let mut cur_unit = source;
663 let mut res = val;
664 while cur_unit > dest {
665 let multiplier: T = match cur_unit {
666 DateTimeField::Millennium => Self::CENTURY_PER_MILLENNIUM.into(),
667 DateTimeField::Century => Self::DECADE_PER_CENTURY.into(),
668 DateTimeField::Decade => Self::YEAR_PER_DECADE.into(),
669 DateTimeField::Year => Self::MONTH_PER_YEAR.into(),
670 DateTimeField::Month => Self::DAY_PER_MONTH.into(),
671 DateTimeField::Day => Self::HOUR_PER_DAY.into(),
672 DateTimeField::Hour => Self::MINUTE_PER_HOUR.into(),
673 DateTimeField::Minute => Self::SECOND_PER_MINUTE.into(),
674 DateTimeField::Second => Self::MILLISECOND_PER_SECOND.into(),
675 DateTimeField::Milliseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
676 DateTimeField::Microseconds => 1.into(),
677 };
678 res = match res.checked_mul(&multiplier) {
679 Some(r) => r,
680 None => return None,
681 };
682 cur_unit = cur_unit.next_smallest();
683 }
684
685 Some(res)
686 }
687
688 pub fn justify_days(&self) -> Result<Self, anyhow::Error> {
690 let days_per_month = i32::from(Self::DAY_PER_MONTH);
691 let (mut months, mut days) = Self::justify_days_inner(self.months, self.days)?;
692 if months > 0 && days < 0 {
693 days += days_per_month;
694 months -= 1;
695 } else if months < 0 && days > 0 {
696 days -= days_per_month;
697 months += 1;
698 }
699
700 Ok(Self::new(months, days, self.micros))
701 }
702
703 fn justify_days_inner(months: i32, days: i32) -> Result<(i32, i32), anyhow::Error> {
704 let days_per_month = i32::from(Self::DAY_PER_MONTH);
705 let whole_month = days / days_per_month;
706 let days = days - whole_month * days_per_month;
707
708 let months = months
709 .checked_add(whole_month)
710 .ok_or_else(|| anyhow!(&*MONTH_OVERFLOW_ERROR))?;
711
712 Ok((months, days))
713 }
714
715 pub fn justify_hours(&self) -> Result<Self, anyhow::Error> {
717 let (mut days, mut micros) = Self::justify_hours_inner(self.days, self.micros)?;
718 if days > 0 && micros < 0 {
719 micros += &*USECS_PER_DAY;
720 days -= 1;
721 } else if days < 0 && micros > 0 {
722 micros -= &*USECS_PER_DAY;
723 days += 1;
724 }
725
726 Ok(Self::new(self.months, days, micros))
727 }
728
729 fn justify_hours_inner(days: i32, micros: i64) -> Result<(i32, i64), anyhow::Error> {
730 let days = i32::try_from(micros / &*USECS_PER_DAY)
731 .ok()
732 .and_then(|d| days.checked_add(d))
733 .ok_or_else(|| anyhow!(&*DAY_OVERFLOW_ERROR))?;
734 let micros = micros % &*USECS_PER_DAY;
735
736 Ok((days, micros))
737 }
738
739 pub fn justify_interval(&self) -> Result<Self, anyhow::Error> {
743 let days_per_month = i32::from(Self::DAY_PER_MONTH);
744 let mut months = self.months;
745 let mut days = self.days;
746 let micros = self.micros;
747 if (days > 0 && micros > 0) || (days < 0 && micros < 0) {
750 let (m, d) = Self::justify_days_inner(self.months, self.days)?;
751 months = m;
752 days = d;
753 }
754 let (days, mut micros) = Self::justify_hours_inner(days, micros)?;
755 let (mut months, mut days) = Self::justify_days_inner(months, days)?;
756
757 if months > 0 && (days < 0 || (days == 0 && micros < 0)) {
758 days += days_per_month;
759 months -= 1;
760 } else if months < 0 && (days > 0 || (days == 0 && micros > 0)) {
761 days -= days_per_month;
762 months += 1;
763 }
764
765 if days > 0 && micros < 0 {
766 micros += &*USECS_PER_DAY;
767 days -= 1;
768 } else if days < 0 && micros > 0 {
769 micros -= &*USECS_PER_DAY;
770 days += 1;
771 }
772
773 Ok(Self::new(months, days, micros))
774 }
775}
776
777impl fmt::Display for Interval {
785 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
786 let neg_months = self.months < 0;
787 let years = (self.months / 12).abs();
788 let months = (self.months % 12).abs();
789
790 let neg_days = self.days < 0;
791 let days = i64::from(self.days).abs();
792
793 let mut nanos = self.nanoseconds().abs();
794 let mut secs = (self.micros / 1_000_000).abs();
795
796 let sec_per_hr = 60 * 60;
797 let hours = secs / sec_per_hr;
798 secs %= sec_per_hr;
799
800 let sec_per_min = 60;
801 let minutes = secs / sec_per_min;
802 secs %= sec_per_min;
803
804 if years > 0 {
805 if neg_months {
806 f.write_char('-')?;
807 }
808 write!(f, "{} year", years)?;
809 if years > 1 || neg_months {
810 f.write_char('s')?;
811 }
812 }
813
814 if months > 0 {
815 if years != 0 {
816 f.write_char(' ')?;
817 }
818 if neg_months {
819 f.write_char('-')?;
820 }
821 write!(f, "{} mon", months)?;
822 if months > 1 || neg_months {
823 f.write_char('s')?;
824 }
825 }
826
827 if days != 0 {
828 if years > 0 || months > 0 {
829 f.write_char(' ')?;
830 }
831 if neg_months && !neg_days {
832 f.write_char('+')?;
833 }
834 write!(f, "{} day", self.days)?;
835 if self.days != 1 {
836 f.write_char('s')?;
837 }
838 }
839
840 let non_zero_hmsn = hours > 0 || minutes > 0 || secs > 0 || nanos > 0;
841
842 if (years == 0 && months == 0 && days == 0) || non_zero_hmsn {
843 if years > 0 || months > 0 || days > 0 {
844 f.write_char(' ')?;
845 }
846 if self.micros < 0 && non_zero_hmsn {
847 f.write_char('-')?;
848 } else if neg_days || (days == 0 && neg_months) {
849 f.write_char('+')?;
850 }
851 write!(f, "{:02}:{:02}:{:02}", hours, minutes, secs)?;
852 if nanos > 0 {
853 let mut width = 9;
854 while nanos % 10 == 0 {
855 width -= 1;
856 nanos /= 10;
857 }
858 write!(f, ".{:0width$}", nanos, width = width)?;
859 }
860 }
861
862 Ok(())
863 }
864}
865
866#[cfg(any(test, feature = "proptest"))]
867impl Arbitrary for Interval {
868 type Strategy = BoxedStrategy<Self>;
869 type Parameters = ();
870
871 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
872 (
873 any::<i32>(),
874 any::<i32>(),
875 ((((i64::from(i32::MIN) * 60) - 59) * 60) * 1_000_000 - 59_999_999
876 ..(((i64::from(i32::MAX) * 60) + 59) * 60) * 1_000_000 + 59_999_999),
877 )
878 .prop_map(|(months, days, micros)| Interval {
879 months,
880 days,
881 micros,
882 })
883 .boxed()
884 }
885}
886
887#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
891pub struct PackedInterval([u8; Self::SIZE]);
892
893#[allow(clippy::as_conversions)]
897impl FixedSizeCodec<Interval> for PackedInterval {
898 const SIZE: usize = 16;
899
900 fn as_bytes(&self) -> &[u8] {
901 &self.0[..]
902 }
903
904 fn from_bytes(slice: &[u8]) -> Result<Self, String> {
905 let buf: [u8; Self::SIZE] = slice.try_into().map_err(|_| {
906 format!(
907 "size for PackedInterval is {} bytes, got {}",
908 Self::SIZE,
909 slice.len()
910 )
911 })?;
912 Ok(PackedInterval(buf))
913 }
914
915 #[inline]
916 fn from_value(value: Interval) -> Self {
917 let mut buf = [0u8; 16];
918
919 let months = (value.months as u32) ^ (0x8000_0000u32);
922 let days = (value.days as u32) ^ (0x8000_0000u32);
923 let micros = (value.micros as u64) ^ (0x8000_0000_0000_0000u64);
924
925 buf[..4].copy_from_slice(&months.to_be_bytes());
926 buf[4..8].copy_from_slice(&days.to_be_bytes());
927 buf[8..].copy_from_slice(µs.to_be_bytes());
928
929 PackedInterval(buf)
930 }
931
932 #[inline]
933 fn into_value(self) -> Interval {
934 let mut months = [0; 4];
937 months.copy_from_slice(&self.0[..4]);
938 let months = u32::from_be_bytes(months) ^ 0x8000_0000u32;
939
940 let mut days = [0; 4];
941 days.copy_from_slice(&self.0[4..8]);
942 let days = u32::from_be_bytes(days) ^ 0x8000_0000u32;
943
944 let mut micros = [0; 8];
945 micros.copy_from_slice(&self.0[8..]);
946 let micros = u64::from_be_bytes(micros) ^ 0x8000_0000_0000_0000u64;
947
948 Interval {
949 months: months as i32,
950 days: days as i32,
951 micros: micros as i64,
952 }
953 }
954}
955
956#[cfg(test)]
957mod test {
958 use mz_ore::assert_ok;
959 use mz_proto::protobuf_roundtrip;
960
961 use super::*;
962 use proptest::prelude::*;
963
964 #[mz_ore::test]
965 fn interval_fmt() {
966 fn mon(mon: i32) -> String {
967 Interval {
968 months: mon,
969 ..Default::default()
970 }
971 .to_string()
972 }
973
974 assert_eq!(mon(1), "1 mon");
975 assert_eq!(mon(12), "1 year");
976 assert_eq!(mon(13), "1 year 1 mon");
977 assert_eq!(mon(24), "2 years");
978 assert_eq!(mon(25), "2 years 1 mon");
979 assert_eq!(mon(26), "2 years 2 mons");
980
981 fn dur(days: i32, micros: i64) -> String {
982 Interval::new(0, days, micros).to_string()
983 }
984 assert_eq!(&dur(2, 0), "2 days");
985 assert_eq!(&dur(2, 3 * 60 * 60 * 1_000_000), "2 days 03:00:00");
986 assert_eq!(
987 &dur(
988 2,
989 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
990 ),
991 "2 days 03:45:06"
992 );
993 assert_eq!(
994 &dur(2, (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000)),
995 "2 days 03:45:00"
996 );
997 assert_eq!(&dur(2, 6 * 1_000_000), "2 days 00:00:06");
998 assert_eq!(
999 &dur(2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1000 "2 days 00:45:06"
1001 );
1002 assert_eq!(
1003 &dur(2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1004 "2 days 03:00:06"
1005 );
1006 assert_eq!(
1007 &dur(
1008 0,
1009 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1010 ),
1011 "03:45:06"
1012 );
1013 assert_eq!(
1014 &dur(0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1015 "03:00:06"
1016 );
1017 assert_eq!(&dur(0, 3 * 60 * 60 * 1_000_000), "03:00:00");
1018 assert_eq!(&dur(0, (45 * 60 * 1_000_000) + (6 * 1_000_000)), "00:45:06");
1019 assert_eq!(&dur(0, 45 * 60 * 1_000_000), "00:45:00");
1020 assert_eq!(&dur(0, 6 * 1_000_000), "00:00:06");
1021
1022 assert_eq!(&dur(-2, -6 * 1_000_000), "-2 days -00:00:06");
1023 assert_eq!(
1024 &dur(-2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1025 "-2 days -00:45:06"
1026 );
1027 assert_eq!(
1028 &dur(-2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1029 "-2 days -03:00:06"
1030 );
1031 assert_eq!(
1032 &dur(
1033 0,
1034 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1035 ),
1036 "-03:45:06"
1037 );
1038 assert_eq!(
1039 &dur(0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1040 "-03:00:06"
1041 );
1042 assert_eq!(&dur(0, -3 * 60 * 60 * 1_000_000), "-03:00:00");
1043 assert_eq!(
1044 &dur(0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1045 "-00:45:06"
1046 );
1047 assert_eq!(&dur(0, -45 * 60 * 1_000_000), "-00:45:00");
1048 assert_eq!(&dur(0, -6 * 1_000_000), "-00:00:06");
1049
1050 fn mon_dur(mon: i32, days: i32, micros: i64) -> String {
1051 Interval::new(mon, days, micros).to_string()
1052 }
1053 assert_eq!(&mon_dur(1, 2, 6 * 1_000_000), "1 mon 2 days 00:00:06");
1054 assert_eq!(
1055 &mon_dur(1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1056 "1 mon 2 days 00:45:06"
1057 );
1058 assert_eq!(
1059 &mon_dur(1, 2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1060 "1 mon 2 days 03:00:06"
1061 );
1062 assert_eq!(
1063 &mon_dur(
1064 26,
1065 0,
1066 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1067 ),
1068 "2 years 2 mons 03:45:06"
1069 );
1070 assert_eq!(
1071 &mon_dur(26, 0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1072 "2 years 2 mons 03:00:06"
1073 );
1074 assert_eq!(
1075 &mon_dur(26, 0, 3 * 60 * 60 * 1_000_000),
1076 "2 years 2 mons 03:00:00"
1077 );
1078 assert_eq!(
1079 &mon_dur(26, 0, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1080 "2 years 2 mons 00:45:06"
1081 );
1082 assert_eq!(
1083 &mon_dur(26, 0, 45 * 60 * 1_000_000),
1084 "2 years 2 mons 00:45:00"
1085 );
1086 assert_eq!(&mon_dur(26, 0, 6 * 1_000_000), "2 years 2 mons 00:00:06");
1087
1088 assert_eq!(
1089 &mon_dur(26, -2, -6 * 1_000_000),
1090 "2 years 2 mons -2 days -00:00:06"
1091 );
1092 assert_eq!(
1093 &mon_dur(26, -2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1094 "2 years 2 mons -2 days -00:45:06"
1095 );
1096 assert_eq!(
1097 &mon_dur(26, -2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1098 "2 years 2 mons -2 days -03:00:06"
1099 );
1100 assert_eq!(
1101 &mon_dur(
1102 26,
1103 0,
1104 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1105 ),
1106 "2 years 2 mons -03:45:06"
1107 );
1108 assert_eq!(
1109 &mon_dur(26, 0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1110 "2 years 2 mons -03:00:06"
1111 );
1112 assert_eq!(
1113 &mon_dur(26, 0, -3 * 60 * 60 * 1_000_000),
1114 "2 years 2 mons -03:00:00"
1115 );
1116 assert_eq!(
1117 &mon_dur(26, 0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1118 "2 years 2 mons -00:45:06"
1119 );
1120 assert_eq!(
1121 &mon_dur(26, 0, -45 * 60 * 1_000_000),
1122 "2 years 2 mons -00:45:00"
1123 );
1124 assert_eq!(&mon_dur(26, 0, -6 * 1_000_000), "2 years 2 mons -00:00:06");
1125
1126 assert_eq!(&mon_dur(-1, 2, 6 * 1_000_000), "-1 mons +2 days 00:00:06");
1127 assert_eq!(
1128 &mon_dur(-1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1129 "-1 mons +2 days 00:45:06"
1130 );
1131 assert_eq!(
1132 &mon_dur(-1, 2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1133 "-1 mons +2 days 03:00:06"
1134 );
1135 assert_eq!(
1136 &mon_dur(
1137 -26,
1138 0,
1139 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1140 ),
1141 "-2 years -2 mons +03:45:06"
1142 );
1143 assert_eq!(
1144 &mon_dur(-26, 0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1145 "-2 years -2 mons +03:00:06"
1146 );
1147 assert_eq!(
1148 &mon_dur(-26, 0, 3 * 60 * 60 * 1_000_000),
1149 "-2 years -2 mons +03:00:00"
1150 );
1151 assert_eq!(
1152 &mon_dur(-26, 0, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1153 "-2 years -2 mons +00:45:06"
1154 );
1155 assert_eq!(
1156 &mon_dur(-26, 0, 45 * 60 * 1_000_000),
1157 "-2 years -2 mons +00:45:00"
1158 );
1159 assert_eq!(
1160 &mon_dur(-26, 0, 6 * 1_000_000),
1161 "-2 years -2 mons +00:00:06"
1162 );
1163
1164 assert_eq!(
1165 &mon_dur(-26, -2, -6 * 1_000_000),
1166 "-2 years -2 mons -2 days -00:00:06"
1167 );
1168 assert_eq!(
1169 &mon_dur(-26, -2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1170 "-2 years -2 mons -2 days -00:45:06"
1171 );
1172 assert_eq!(
1173 &mon_dur(-26, -2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1174 "-2 years -2 mons -2 days -03:00:06"
1175 );
1176 assert_eq!(
1177 &mon_dur(
1178 -26,
1179 0,
1180 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1181 ),
1182 "-2 years -2 mons -03:45:06"
1183 );
1184 assert_eq!(
1185 &mon_dur(-26, 0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1186 "-2 years -2 mons -03:00:06"
1187 );
1188 assert_eq!(
1189 &mon_dur(-26, 0, -3 * 60 * 60 * 1_000_000),
1190 "-2 years -2 mons -03:00:00"
1191 );
1192 assert_eq!(
1193 &mon_dur(-26, 0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1194 "-2 years -2 mons -00:45:06"
1195 );
1196 assert_eq!(
1197 &mon_dur(-26, 0, -45 * 60 * 1_000_000),
1198 "-2 years -2 mons -00:45:00"
1199 );
1200 assert_eq!(
1201 &mon_dur(-26, 0, -6 * 1_000_000),
1202 "-2 years -2 mons -00:00:06"
1203 );
1204 }
1205
1206 #[mz_ore::test]
1207 fn test_interval_value_truncate_low_fields() {
1208 use DateTimeField::*;
1209
1210 let mut test_cases = [
1211 (
1212 Year,
1213 None,
1214 (
1215 321,
1216 7,
1217 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1218 ),
1219 (26 * 12, 0, 0),
1220 ),
1221 (
1222 Month,
1223 None,
1224 (
1225 321,
1226 7,
1227 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1228 ),
1229 (321, 0, 0),
1230 ),
1231 (
1232 Day,
1233 None,
1234 (
1235 321,
1236 7,
1237 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1238 ),
1239 (321, 7, 0),
1240 ),
1241 (
1242 Hour,
1243 None,
1244 (
1245 321,
1246 7,
1247 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1248 ),
1249 (321, 7, 13 * 60 * 60 * 1_000_000),
1250 ),
1251 (
1252 Minute,
1253 None,
1254 (
1255 321,
1256 7,
1257 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1258 ),
1259 (321, 7, (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000)),
1260 ),
1261 (
1262 Second,
1263 None,
1264 (
1265 321,
1266 7,
1267 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1268 ),
1269 (
1270 321,
1271 7,
1272 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1273 ),
1274 ),
1275 (
1276 Second,
1277 Some(1),
1278 (
1279 321,
1280 7,
1281 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1282 ),
1283 (
1284 321,
1285 7,
1286 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 300_000,
1287 ),
1288 ),
1289 (
1290 Second,
1291 Some(0),
1292 (
1293 321,
1294 7,
1295 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1296 ),
1297 (
1298 321,
1299 7,
1300 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1301 ),
1302 ),
1303 ];
1304
1305 for test in test_cases.iter_mut() {
1306 let mut i = Interval::new((test.2).0, (test.2).1, (test.2).2);
1307 let j = Interval::new((test.3).0, (test.3).1, (test.3).2);
1308
1309 i.truncate_low_fields(test.0, test.1, RoundBehavior::Nearest)
1310 .unwrap();
1311
1312 if i != j {
1313 panic!(
1314 "test_interval_value_truncate_low_fields failed on {} \n actual: {:?} \n expected: {:?}",
1315 test.0, i, j
1316 );
1317 }
1318 }
1319 }
1320
1321 #[mz_ore::test]
1322 fn test_convert_date_time_unit() {
1323 assert_eq!(
1324 Some(1_123_200_000_000),
1325 Interval::convert_date_time_unit(
1326 DateTimeField::Day,
1327 DateTimeField::Microseconds,
1328 13i64
1329 )
1330 );
1331
1332 assert_eq!(
1333 Some(3_558_399_705),
1334 Interval::convert_date_time_unit(
1335 DateTimeField::Milliseconds,
1336 DateTimeField::Month,
1337 i64::MAX
1338 )
1339 );
1340
1341 assert_eq!(
1342 None,
1343 Interval::convert_date_time_unit(
1344 DateTimeField::Minute,
1345 DateTimeField::Second,
1346 i32::MAX
1347 )
1348 );
1349
1350 assert_eq!(
1351 Some(1),
1352 Interval::convert_date_time_unit(DateTimeField::Day, DateTimeField::Year, 365)
1353 );
1354
1355 assert_eq!(
1357 Some(360),
1358 Interval::convert_date_time_unit(DateTimeField::Year, DateTimeField::Day, 1)
1359 );
1360 }
1361
1362 #[mz_ore::test]
1363 fn proptest_packed_interval_roundtrips() {
1364 fn roundtrip_interval(og: Interval) {
1365 let packed = PackedInterval::from_value(og);
1366 let rnd = packed.into_value();
1367
1368 assert_eq!(og, rnd);
1369 }
1370
1371 proptest!(|(interval in any::<Interval>())| {
1372 roundtrip_interval(interval);
1373 });
1374 }
1375
1376 #[mz_ore::test]
1377 #[cfg_attr(miri, ignore)] fn proptest_packed_interval_sorts() {
1379 fn sort_intervals(mut og: Vec<Interval>) {
1380 let mut packed: Vec<_> = og.iter().copied().map(PackedInterval::from_value).collect();
1381
1382 og.sort();
1383 packed.sort();
1384
1385 let rnd: Vec<_> = packed.into_iter().map(PackedInterval::into_value).collect();
1386
1387 assert_eq!(og, rnd);
1388 }
1389
1390 proptest!(|(interval in any::<Vec<Interval>>())| {
1391 sort_intervals(interval);
1392 });
1393 }
1394
1395 proptest! {
1401 #[mz_ore::test]
1402 fn interval_protobuf_roundtrip(expect in any::<Interval>()) {
1403 let actual = protobuf_roundtrip::<_, ProtoInterval>(&expect);
1404 assert_ok!(actual);
1405 assert_eq!(actual.unwrap(), expect);
1406 }
1407 }
1408}