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};
26
27include!(concat!(env!("OUT_DIR"), "/mz_repr.adt.interval.rs"));
28
29#[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 pub months: i32,
47 pub days: i32,
51 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 pub const DAY_PER_MONTH: u16 = 30;
139 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 pub const EPOCH_DAYS_PER_YEAR: f64 = 365.25;
152
153 pub const fn new(months: i32, days: i32, micros: i64) -> Interval {
155 Interval {
156 months,
157 days,
158 micros,
159 }
160 }
161
162 pub fn try_new(months: i128, days: i128, micros: i128) -> Option<Interval> {
168 Some(Interval::new(
169 i32::try_from(months).ok()?,
170 i32::try_from(days).ok()?,
171 i64::try_from(micros).ok()?,
172 ))
173 }
174
175 pub fn from_duration(duration: &Duration) -> Result<Interval, anyhow::Error> {
180 if duration.subsec_nanos() % 1000 != 0 {
181 return Err(anyhow!(
182 "cannot convert Duration to Interval due to fractional microseconds"
183 ));
184 }
185 Ok(Interval {
186 months: 0,
187 days: 0,
188 micros: duration.as_micros().try_into()?,
189 })
190 }
191
192 pub fn from_chrono_duration(duration: chrono::Duration) -> Result<Self, anyhow::Error> {
195 let Some(micros) = duration.num_microseconds() else {
196 bail!("cannot convert Duration to Interval due to overflowed microseconds");
197 };
198 Ok(Self {
199 months: 0,
200 days: 0,
201 micros,
202 })
203 }
204
205 pub fn checked_add(&self, other: &Self) -> Option<Self> {
206 let months = match self.months.checked_add(other.months) {
207 Some(m) => m,
208 None => return None,
209 };
210 let days = match self.days.checked_add(other.days) {
211 Some(d) => d,
212 None => return None,
213 };
214 let micros = match self.micros.checked_add(other.micros) {
215 Some(us) => us,
216 None => return None,
217 };
218
219 Some(Self::new(months, days, micros))
220 }
221
222 pub fn checked_mul(&self, other: f64) -> Option<Self> {
223 self.checked_op(other, |f1, f2| f1 * f2)
224 }
225
226 pub fn checked_div(&self, other: f64) -> Option<Self> {
227 self.checked_op(other, |f1, f2| f1 / f2)
228 }
229
230 #[allow(clippy::as_conversions)]
233 fn checked_op<F1>(&self, other: f64, op: F1) -> Option<Self>
234 where
235 F1: Fn(f64, f64) -> f64,
236 {
237 let months = op(f64::from(self.months), other);
238 if months.is_nan()
239 || months.is_infinite()
240 || months < i32::MIN.into()
241 || months > i32::MAX.into()
242 {
243 return None;
244 }
245
246 let days =
247 op(f64::from(self.days), other) + months.fract() * f64::from(Self::DAY_PER_MONTH);
248 if days.is_nan() || days.is_infinite() || days < i32::MIN.into() || days > i32::MAX.into() {
249 return None;
250 }
251
252 let micros = op(self.micros as f64, other)
253 + days.fract()
254 * f64::from(Self::HOUR_PER_DAY)
255 * f64::from(Self::MINUTE_PER_HOUR)
256 * f64::from(Self::SECOND_PER_MINUTE)
257 * f64::from(Self::MILLISECOND_PER_SECOND)
258 * f64::from(Self::MICROSECOND_PER_MILLISECOND);
259
260 if micros.is_nan()
261 || micros.is_infinite()
262 || Numeric::from(micros) < Numeric::from(i64::MIN)
263 || Numeric::from(micros) > Numeric::from(i64::MAX)
264 {
265 return None;
266 }
267
268 Some(Self::new(months as i32, days as i32, micros as i64))
269 }
270
271 pub fn millennia(&self) -> i32 {
276 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Millennium, self.months)
277 .unwrap()
278 }
279
280 pub fn centuries(&self) -> i32 {
285 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Century, self.months)
286 .unwrap()
287 }
288
289 pub fn decades(&self) -> i32 {
294 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Decade, self.months)
295 .unwrap()
296 }
297
298 pub fn years(&self) -> i32 {
303 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Year, self.months)
304 .unwrap()
305 }
306
307 pub fn quarters(&self) -> i32 {
313 self.months() / 3 + 1
314 }
315
316 pub fn months(&self) -> i32 {
322 self.months % i32::from(Self::MONTH_PER_YEAR)
323 }
324
325 pub fn days(&self) -> i64 {
331 self.days.into()
332 }
333
334 pub fn hours(&self) -> i64 {
340 Self::convert_date_time_unit(
341 DateTimeField::Microseconds,
342 DateTimeField::Hour,
343 self.micros,
344 )
345 .unwrap()
346 % i64::from(Self::HOUR_PER_DAY)
347 }
348
349 pub fn minutes(&self) -> i64 {
355 Self::convert_date_time_unit(
356 DateTimeField::Microseconds,
357 DateTimeField::Minute,
358 self.micros,
359 )
360 .unwrap()
361 % i64::from(Self::MINUTE_PER_HOUR)
362 }
363
364 pub fn seconds<T>(&self) -> T
369 where
370 T: DecimalLike,
371 {
372 T::lossy_from(self.micros % 60_000_000) / T::from(1e6)
373 }
374
375 pub fn milliseconds<T>(&self) -> T
380 where
381 T: DecimalLike,
382 {
383 T::lossy_from(self.micros % 60_000_000) / T::from(1e3)
384 }
385
386 pub fn microseconds<T>(&self) -> T
391 where
392 T: DecimalLike,
393 {
394 T::lossy_from(self.micros % 60_000_000)
395 }
396
397 pub fn nanoseconds(&self) -> i32 {
399 (self.micros % 1_000_000 * 1_000).try_into().unwrap()
400 }
401
402 pub fn as_epoch_seconds<T>(&self) -> T
406 where
407 T: DecimalLike,
408 {
409 let days = T::from(self.years()) * T::from(Self::EPOCH_DAYS_PER_YEAR)
410 + T::from(self.months()) * T::from(Self::DAY_PER_MONTH)
411 + T::from(self.days);
412 let seconds = days
413 * T::from(Self::HOUR_PER_DAY)
414 * T::from(Self::MINUTE_PER_HOUR)
415 * T::from(Self::SECOND_PER_MINUTE);
416
417 seconds
418 + T::lossy_from(self.micros)
419 / (T::from(Self::MICROSECOND_PER_MILLISECOND)
420 * T::from(Self::MILLISECOND_PER_SECOND))
421 }
422
423 pub fn as_microseconds(&self) -> i128 {
425 Self::convert_date_time_unit(
428 DateTimeField::Month,
429 DateTimeField::Microseconds,
430 i128::from(self.months),
431 ).unwrap() +
432 Self::convert_date_time_unit(
435 DateTimeField::Day,
436 DateTimeField::Microseconds,
437 i128::from(self.days),
438 ).unwrap() +
439 i128::from(self.micros)
440 }
441
442 pub fn as_milliseconds(&self) -> i128 {
444 self.as_microseconds() / 1000
445 }
446
447 pub fn duration_as_chrono(&self) -> chrono::Duration {
449 use chrono::Duration;
450 Duration::try_days(self.days.into()).unwrap() + Duration::microseconds(self.micros)
451 }
452
453 pub fn duration(&self) -> Result<Duration, anyhow::Error> {
454 if self.months != 0 {
455 bail!("cannot convert interval with months to duration");
456 }
457 if self.is_negative() {
458 bail!("cannot convert negative interval to duration");
459 }
460 let Ok(micros) = u64::try_from(self.as_microseconds()) else {
465 bail!("interval is too large to convert to duration");
466 };
467 Ok(Duration::from_micros(micros))
468 }
469
470 pub fn truncate_low_fields(
479 &mut self,
480 f: DateTimeField,
481 fsec_max_precision: Option<u64>,
482 round_behavior: RoundBehavior,
483 ) -> Result<(), anyhow::Error> {
484 use DateTimeField::*;
485 match f {
486 Millennium => {
487 self.months -= self.months % (12 * 1000);
488 self.days = 0;
489 self.micros = 0;
490 }
491 Century => {
492 self.months -= self.months % (12 * 100);
493 self.days = 0;
494 self.micros = 0;
495 }
496 Decade => {
497 self.months -= self.months % (12 * 10);
498 self.days = 0;
499 self.micros = 0;
500 }
501 Year => {
502 self.months -= self.months % 12;
503 self.days = 0;
504 self.micros = 0;
505 }
506 Month => {
507 self.days = 0;
508 self.micros = 0;
509 }
510 Second => {
512 let default_precision = 6;
513 let precision = match fsec_max_precision {
514 Some(p) => p,
515 None => default_precision,
516 };
517
518 if precision > default_precision {
519 bail!(
520 "SECOND precision must be (0, 6), have SECOND({})",
521 precision
522 )
523 }
524
525 let precision = match u32::try_from(precision) {
526 Ok(p) => p,
527 Err(_) => bail!(
528 "SECOND precision must be (0, 6), have SECOND({})",
529 precision
530 ),
531 };
532 let remainder = self.micros % 10_i64.pow(6 - precision);
534 self.micros -= remainder;
535 if matches!(round_behavior, RoundBehavior::Nearest)
537 && u64::from(precision) != default_precision
538 {
539 let rounding_digit = remainder / 10_i64.pow(5 - precision);
540 let micros = if rounding_digit > 4 {
541 self.micros.checked_add(10_i64.pow(6 - precision))
542 } else if rounding_digit < -4 {
543 self.micros.checked_sub(10_i64.pow(6 - precision))
544 } else {
545 Some(self.micros)
546 };
547 let Some(micros) = micros else {
548 bail!("interval field value out of range: \"{self}\"");
549 };
550 self.micros = micros;
551 }
552 }
553 Day => {
554 self.micros = 0;
555 }
556 Hour | Minute | Milliseconds | Microseconds => {
557 self.micros -= self.micros % f.micros_multiplier();
558 }
559 }
560 Ok(())
561 }
562
563 pub fn as_time_interval(&self) -> Self {
565 Self::new(0, 0, self.micros)
566 }
567
568 pub fn is_negative(&self) -> bool {
570 self.as_microseconds() < 0
571 }
572
573 pub fn convert_date_time_unit<T>(
580 source: DateTimeField,
581 dest: DateTimeField,
582 val: T,
583 ) -> Option<T>
584 where
585 T: From<u16> + CheckedMul + std::ops::DivAssign,
586 {
587 if source < dest {
588 Self::convert_date_time_unit_increasing(source, dest, val)
589 } else if source > dest {
590 Self::convert_date_time_unit_decreasing(source, dest, val)
591 } else {
592 Some(val)
593 }
594 }
595
596 fn convert_date_time_unit_increasing<T>(
597 source: DateTimeField,
598 dest: DateTimeField,
599 val: T,
600 ) -> Option<T>
601 where
602 T: From<u16> + std::ops::DivAssign,
603 {
604 let mut cur_unit = source;
605 let mut res = val;
606 while cur_unit < dest {
607 let divisor: T = match cur_unit {
608 DateTimeField::Millennium => 1.into(),
609 DateTimeField::Century => Self::CENTURY_PER_MILLENNIUM.into(),
610 DateTimeField::Decade => Self::DECADE_PER_CENTURY.into(),
611 DateTimeField::Year => Self::YEAR_PER_DECADE.into(),
612 DateTimeField::Month => Self::MONTH_PER_YEAR.into(),
613 DateTimeField::Day => Self::DAY_PER_MONTH.into(),
614 DateTimeField::Hour => Self::HOUR_PER_DAY.into(),
615 DateTimeField::Minute => Self::MINUTE_PER_HOUR.into(),
616 DateTimeField::Second => Self::SECOND_PER_MINUTE.into(),
617 DateTimeField::Milliseconds => Self::MILLISECOND_PER_SECOND.into(),
618 DateTimeField::Microseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
619 };
620 res /= divisor;
621 cur_unit = cur_unit.next_largest();
622 }
623
624 Some(res)
625 }
626
627 fn convert_date_time_unit_decreasing<T>(
628 source: DateTimeField,
629 dest: DateTimeField,
630 val: T,
631 ) -> Option<T>
632 where
633 T: From<u16> + CheckedMul,
634 {
635 let mut cur_unit = source;
636 let mut res = val;
637 while cur_unit > dest {
638 let multiplier: T = match cur_unit {
639 DateTimeField::Millennium => Self::CENTURY_PER_MILLENNIUM.into(),
640 DateTimeField::Century => Self::DECADE_PER_CENTURY.into(),
641 DateTimeField::Decade => Self::YEAR_PER_DECADE.into(),
642 DateTimeField::Year => Self::MONTH_PER_YEAR.into(),
643 DateTimeField::Month => Self::DAY_PER_MONTH.into(),
644 DateTimeField::Day => Self::HOUR_PER_DAY.into(),
645 DateTimeField::Hour => Self::MINUTE_PER_HOUR.into(),
646 DateTimeField::Minute => Self::SECOND_PER_MINUTE.into(),
647 DateTimeField::Second => Self::MILLISECOND_PER_SECOND.into(),
648 DateTimeField::Milliseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
649 DateTimeField::Microseconds => 1.into(),
650 };
651 res = match res.checked_mul(&multiplier) {
652 Some(r) => r,
653 None => return None,
654 };
655 cur_unit = cur_unit.next_smallest();
656 }
657
658 Some(res)
659 }
660
661 pub fn justify_days(&self) -> Result<Self, anyhow::Error> {
663 let days_per_month = i32::from(Self::DAY_PER_MONTH);
664 let (mut months, mut days) = Self::justify_days_inner(self.months, self.days)?;
665 if months > 0 && days < 0 {
666 days += days_per_month;
667 months -= 1;
668 } else if months < 0 && days > 0 {
669 days -= days_per_month;
670 months += 1;
671 }
672
673 Ok(Self::new(months, days, self.micros))
674 }
675
676 fn justify_days_inner(months: i32, days: i32) -> Result<(i32, i32), anyhow::Error> {
677 let days_per_month = i32::from(Self::DAY_PER_MONTH);
678 let whole_month = days / days_per_month;
679 let days = days - whole_month * days_per_month;
680
681 let months = months
682 .checked_add(whole_month)
683 .ok_or_else(|| anyhow!(&*MONTH_OVERFLOW_ERROR))?;
684
685 Ok((months, days))
686 }
687
688 pub fn justify_hours(&self) -> Result<Self, anyhow::Error> {
690 let (mut days, mut micros) = Self::justify_hours_inner(self.days, self.micros)?;
691 if days > 0 && micros < 0 {
692 micros += &*USECS_PER_DAY;
693 days -= 1;
694 } else if days < 0 && micros > 0 {
695 micros -= &*USECS_PER_DAY;
696 days += 1;
697 }
698
699 Ok(Self::new(self.months, days, micros))
700 }
701
702 fn justify_hours_inner(days: i32, micros: i64) -> Result<(i32, i64), anyhow::Error> {
703 let days = i32::try_from(micros / &*USECS_PER_DAY)
704 .ok()
705 .and_then(|d| days.checked_add(d))
706 .ok_or_else(|| anyhow!(&*DAY_OVERFLOW_ERROR))?;
707 let micros = micros % &*USECS_PER_DAY;
708
709 Ok((days, micros))
710 }
711
712 pub fn justify_interval(&self) -> Result<Self, anyhow::Error> {
716 let days_per_month = i32::from(Self::DAY_PER_MONTH);
717 let mut months = self.months;
718 let mut days = self.days;
719 let micros = self.micros;
720 if (days > 0 && micros > 0) || (days < 0 && micros < 0) {
723 let (m, d) = Self::justify_days_inner(self.months, self.days)?;
724 months = m;
725 days = d;
726 }
727 let (days, mut micros) = Self::justify_hours_inner(days, micros)?;
728 let (mut months, mut days) = Self::justify_days_inner(months, days)?;
729
730 if months > 0 && (days < 0 || (days == 0 && micros < 0)) {
731 days += days_per_month;
732 months -= 1;
733 } else if months < 0 && (days > 0 || (days == 0 && micros > 0)) {
734 days -= days_per_month;
735 months += 1;
736 }
737
738 if days > 0 && micros < 0 {
739 micros += &*USECS_PER_DAY;
740 days -= 1;
741 } else if days < 0 && micros > 0 {
742 micros -= &*USECS_PER_DAY;
743 days += 1;
744 }
745
746 Ok(Self::new(months, days, micros))
747 }
748}
749
750impl fmt::Display for Interval {
758 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
759 let neg_months = self.months < 0;
760 let years = (self.months / 12).abs();
761 let months = (self.months % 12).abs();
762
763 let neg_days = self.days < 0;
764 let days = i64::from(self.days).abs();
765
766 let mut nanos = self.nanoseconds().abs();
767 let mut secs = (self.micros / 1_000_000).abs();
768
769 let sec_per_hr = 60 * 60;
770 let hours = secs / sec_per_hr;
771 secs %= sec_per_hr;
772
773 let sec_per_min = 60;
774 let minutes = secs / sec_per_min;
775 secs %= sec_per_min;
776
777 if years > 0 {
778 if neg_months {
779 f.write_char('-')?;
780 }
781 write!(f, "{} year", years)?;
782 if years > 1 || neg_months {
783 f.write_char('s')?;
784 }
785 }
786
787 if months > 0 {
788 if years != 0 {
789 f.write_char(' ')?;
790 }
791 if neg_months {
792 f.write_char('-')?;
793 }
794 write!(f, "{} mon", months)?;
795 if months > 1 || neg_months {
796 f.write_char('s')?;
797 }
798 }
799
800 if days != 0 {
801 if years > 0 || months > 0 {
802 f.write_char(' ')?;
803 }
804 if neg_months && !neg_days {
805 f.write_char('+')?;
806 }
807 write!(f, "{} day", self.days)?;
808 if self.days != 1 {
809 f.write_char('s')?;
810 }
811 }
812
813 let non_zero_hmsn = hours > 0 || minutes > 0 || secs > 0 || nanos > 0;
814
815 if (years == 0 && months == 0 && days == 0) || non_zero_hmsn {
816 if years > 0 || months > 0 || days > 0 {
817 f.write_char(' ')?;
818 }
819 if self.micros < 0 && non_zero_hmsn {
820 f.write_char('-')?;
821 } else if neg_days || (days == 0 && neg_months) {
822 f.write_char('+')?;
823 }
824 write!(f, "{:02}:{:02}:{:02}", hours, minutes, secs)?;
825 if nanos > 0 {
826 let mut width = 9;
827 while nanos % 10 == 0 {
828 width -= 1;
829 nanos /= 10;
830 }
831 write!(f, ".{:0width$}", nanos, width = width)?;
832 }
833 }
834
835 Ok(())
836 }
837}
838
839#[cfg(any(test, feature = "proptest"))]
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#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
864pub struct PackedInterval([u8; Self::SIZE]);
865
866#[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 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(µs.to_be_bytes());
901
902 PackedInterval(buf)
903 }
904
905 #[inline]
906 fn into_value(self) -> Interval {
907 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 mz_ore::assert_ok;
932 use mz_proto::protobuf_roundtrip;
933
934 use super::*;
935 use proptest::prelude::*;
936
937 #[mz_ore::test]
938 fn interval_fmt() {
939 fn mon(mon: i32) -> String {
940 Interval {
941 months: mon,
942 ..Default::default()
943 }
944 .to_string()
945 }
946
947 assert_eq!(mon(1), "1 mon");
948 assert_eq!(mon(12), "1 year");
949 assert_eq!(mon(13), "1 year 1 mon");
950 assert_eq!(mon(24), "2 years");
951 assert_eq!(mon(25), "2 years 1 mon");
952 assert_eq!(mon(26), "2 years 2 mons");
953
954 fn dur(days: i32, micros: i64) -> String {
955 Interval::new(0, days, micros).to_string()
956 }
957 assert_eq!(&dur(2, 0), "2 days");
958 assert_eq!(&dur(2, 3 * 60 * 60 * 1_000_000), "2 days 03:00:00");
959 assert_eq!(
960 &dur(
961 2,
962 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
963 ),
964 "2 days 03:45:06"
965 );
966 assert_eq!(
967 &dur(2, (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000)),
968 "2 days 03:45:00"
969 );
970 assert_eq!(&dur(2, 6 * 1_000_000), "2 days 00:00:06");
971 assert_eq!(
972 &dur(2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
973 "2 days 00:45:06"
974 );
975 assert_eq!(
976 &dur(2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
977 "2 days 03:00:06"
978 );
979 assert_eq!(
980 &dur(
981 0,
982 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
983 ),
984 "03:45:06"
985 );
986 assert_eq!(
987 &dur(0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
988 "03:00:06"
989 );
990 assert_eq!(&dur(0, 3 * 60 * 60 * 1_000_000), "03:00:00");
991 assert_eq!(&dur(0, (45 * 60 * 1_000_000) + (6 * 1_000_000)), "00:45:06");
992 assert_eq!(&dur(0, 45 * 60 * 1_000_000), "00:45:00");
993 assert_eq!(&dur(0, 6 * 1_000_000), "00:00:06");
994
995 assert_eq!(&dur(-2, -6 * 1_000_000), "-2 days -00:00:06");
996 assert_eq!(
997 &dur(-2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
998 "-2 days -00:45:06"
999 );
1000 assert_eq!(
1001 &dur(-2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1002 "-2 days -03:00:06"
1003 );
1004 assert_eq!(
1005 &dur(
1006 0,
1007 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1008 ),
1009 "-03:45:06"
1010 );
1011 assert_eq!(
1012 &dur(0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1013 "-03:00:06"
1014 );
1015 assert_eq!(&dur(0, -3 * 60 * 60 * 1_000_000), "-03:00:00");
1016 assert_eq!(
1017 &dur(0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1018 "-00:45:06"
1019 );
1020 assert_eq!(&dur(0, -45 * 60 * 1_000_000), "-00:45:00");
1021 assert_eq!(&dur(0, -6 * 1_000_000), "-00:00:06");
1022
1023 fn mon_dur(mon: i32, days: i32, micros: i64) -> String {
1024 Interval::new(mon, days, micros).to_string()
1025 }
1026 assert_eq!(&mon_dur(1, 2, 6 * 1_000_000), "1 mon 2 days 00:00:06");
1027 assert_eq!(
1028 &mon_dur(1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1029 "1 mon 2 days 00:45:06"
1030 );
1031 assert_eq!(
1032 &mon_dur(1, 2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1033 "1 mon 2 days 03:00:06"
1034 );
1035 assert_eq!(
1036 &mon_dur(
1037 26,
1038 0,
1039 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1040 ),
1041 "2 years 2 mons 03:45:06"
1042 );
1043 assert_eq!(
1044 &mon_dur(26, 0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1045 "2 years 2 mons 03:00:06"
1046 );
1047 assert_eq!(
1048 &mon_dur(26, 0, 3 * 60 * 60 * 1_000_000),
1049 "2 years 2 mons 03:00:00"
1050 );
1051 assert_eq!(
1052 &mon_dur(26, 0, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1053 "2 years 2 mons 00:45:06"
1054 );
1055 assert_eq!(
1056 &mon_dur(26, 0, 45 * 60 * 1_000_000),
1057 "2 years 2 mons 00:45:00"
1058 );
1059 assert_eq!(&mon_dur(26, 0, 6 * 1_000_000), "2 years 2 mons 00:00:06");
1060
1061 assert_eq!(
1062 &mon_dur(26, -2, -6 * 1_000_000),
1063 "2 years 2 mons -2 days -00:00:06"
1064 );
1065 assert_eq!(
1066 &mon_dur(26, -2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1067 "2 years 2 mons -2 days -00:45:06"
1068 );
1069 assert_eq!(
1070 &mon_dur(26, -2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1071 "2 years 2 mons -2 days -03:00:06"
1072 );
1073 assert_eq!(
1074 &mon_dur(
1075 26,
1076 0,
1077 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1078 ),
1079 "2 years 2 mons -03:45:06"
1080 );
1081 assert_eq!(
1082 &mon_dur(26, 0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1083 "2 years 2 mons -03:00:06"
1084 );
1085 assert_eq!(
1086 &mon_dur(26, 0, -3 * 60 * 60 * 1_000_000),
1087 "2 years 2 mons -03:00:00"
1088 );
1089 assert_eq!(
1090 &mon_dur(26, 0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1091 "2 years 2 mons -00:45:06"
1092 );
1093 assert_eq!(
1094 &mon_dur(26, 0, -45 * 60 * 1_000_000),
1095 "2 years 2 mons -00:45:00"
1096 );
1097 assert_eq!(&mon_dur(26, 0, -6 * 1_000_000), "2 years 2 mons -00:00:06");
1098
1099 assert_eq!(&mon_dur(-1, 2, 6 * 1_000_000), "-1 mons +2 days 00:00:06");
1100 assert_eq!(
1101 &mon_dur(-1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1102 "-1 mons +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 mons +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 mons +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 mons +03:00:06"
1119 );
1120 assert_eq!(
1121 &mon_dur(-26, 0, 3 * 60 * 60 * 1_000_000),
1122 "-2 years -2 mons +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 mons +00:45:06"
1127 );
1128 assert_eq!(
1129 &mon_dur(-26, 0, 45 * 60 * 1_000_000),
1130 "-2 years -2 mons +00:45:00"
1131 );
1132 assert_eq!(
1133 &mon_dur(-26, 0, 6 * 1_000_000),
1134 "-2 years -2 mons +00:00:06"
1135 );
1136
1137 assert_eq!(
1138 &mon_dur(-26, -2, -6 * 1_000_000),
1139 "-2 years -2 mons -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 mons -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 mons -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 mons -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 mons -03:00:06"
1160 );
1161 assert_eq!(
1162 &mon_dur(-26, 0, -3 * 60 * 60 * 1_000_000),
1163 "-2 years -2 mons -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 mons -00:45:06"
1168 );
1169 assert_eq!(
1170 &mon_dur(-26, 0, -45 * 60 * 1_000_000),
1171 "-2 years -2 mons -00:45:00"
1172 );
1173 assert_eq!(
1174 &mon_dur(-26, 0, -6 * 1_000_000),
1175 "-2 years -2 mons -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_convert_date_time_unit() {
1296 assert_eq!(
1297 Some(1_123_200_000_000),
1298 Interval::convert_date_time_unit(
1299 DateTimeField::Day,
1300 DateTimeField::Microseconds,
1301 13i64
1302 )
1303 );
1304
1305 assert_eq!(
1306 Some(3_558_399_705),
1307 Interval::convert_date_time_unit(
1308 DateTimeField::Milliseconds,
1309 DateTimeField::Month,
1310 i64::MAX
1311 )
1312 );
1313
1314 assert_eq!(
1315 None,
1316 Interval::convert_date_time_unit(
1317 DateTimeField::Minute,
1318 DateTimeField::Second,
1319 i32::MAX
1320 )
1321 );
1322
1323 assert_eq!(
1324 Some(1),
1325 Interval::convert_date_time_unit(DateTimeField::Day, DateTimeField::Year, 365)
1326 );
1327
1328 assert_eq!(
1330 Some(360),
1331 Interval::convert_date_time_unit(DateTimeField::Year, DateTimeField::Day, 1)
1332 );
1333 }
1334
1335 #[mz_ore::test]
1336 fn proptest_packed_interval_roundtrips() {
1337 fn roundtrip_interval(og: Interval) {
1338 let packed = PackedInterval::from_value(og);
1339 let rnd = packed.into_value();
1340
1341 assert_eq!(og, rnd);
1342 }
1343
1344 proptest!(|(interval in any::<Interval>())| {
1345 roundtrip_interval(interval);
1346 });
1347 }
1348
1349 #[mz_ore::test]
1350 #[cfg_attr(miri, ignore)] fn proptest_packed_interval_sorts() {
1352 fn sort_intervals(mut og: Vec<Interval>) {
1353 let mut packed: Vec<_> = og.iter().copied().map(PackedInterval::from_value).collect();
1354
1355 og.sort();
1356 packed.sort();
1357
1358 let rnd: Vec<_> = packed.into_iter().map(PackedInterval::into_value).collect();
1359
1360 assert_eq!(og, rnd);
1361 }
1362
1363 proptest!(|(interval in any::<Vec<Interval>>())| {
1364 sort_intervals(interval);
1365 });
1366 }
1367
1368 proptest! {
1374 #[mz_ore::test]
1375 fn interval_protobuf_roundtrip(expect in any::<Interval>()) {
1376 let actual = protobuf_roundtrip::<_, ProtoInterval>(&expect);
1377 assert_ok!(actual);
1378 assert_eq!(actual.unwrap(), expect);
1379 }
1380 }
1381}