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 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 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 #[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 pub fn millennia(&self) -> i32 {
263 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Millennium, self.months)
264 .unwrap()
265 }
266
267 pub fn centuries(&self) -> i32 {
272 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Century, self.months)
273 .unwrap()
274 }
275
276 pub fn decades(&self) -> i32 {
281 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Decade, self.months)
282 .unwrap()
283 }
284
285 pub fn years(&self) -> i32 {
290 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Year, self.months)
291 .unwrap()
292 }
293
294 pub fn quarters(&self) -> i32 {
300 self.months() / 3 + 1
301 }
302
303 pub fn months(&self) -> i32 {
309 self.months % i32::from(Self::MONTH_PER_YEAR)
310 }
311
312 pub fn days(&self) -> i64 {
318 self.days.into()
319 }
320
321 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 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 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 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 pub fn microseconds<T>(&self) -> T
378 where
379 T: DecimalLike,
380 {
381 T::lossy_from(self.micros % 60_000_000)
382 }
383
384 pub fn nanoseconds(&self) -> i32 {
386 (self.micros % 1_000_000 * 1_000).try_into().unwrap()
387 }
388
389 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 pub fn as_microseconds(&self) -> i128 {
412 Self::convert_date_time_unit(
415 DateTimeField::Month,
416 DateTimeField::Microseconds,
417 i128::from(self.months),
418 ).unwrap() +
419 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 pub fn as_milliseconds(&self) -> i128 {
431 self.as_microseconds() / 1000
432 }
433
434 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 Ok(micros) = u64::try_from(self.as_microseconds()) else {
452 bail!("interval is too large to convert to duration");
453 };
454 Ok(Duration::from_micros(micros))
455 }
456
457 pub fn truncate_low_fields(
466 &mut self,
467 f: DateTimeField,
468 fsec_max_precision: Option<u64>,
469 round_behavior: RoundBehavior,
470 ) -> Result<(), anyhow::Error> {
471 use DateTimeField::*;
472 match f {
473 Millennium => {
474 self.months -= self.months % (12 * 1000);
475 self.days = 0;
476 self.micros = 0;
477 }
478 Century => {
479 self.months -= self.months % (12 * 100);
480 self.days = 0;
481 self.micros = 0;
482 }
483 Decade => {
484 self.months -= self.months % (12 * 10);
485 self.days = 0;
486 self.micros = 0;
487 }
488 Year => {
489 self.months -= self.months % 12;
490 self.days = 0;
491 self.micros = 0;
492 }
493 Month => {
494 self.days = 0;
495 self.micros = 0;
496 }
497 Second => {
499 let default_precision = 6;
500 let precision = match fsec_max_precision {
501 Some(p) => p,
502 None => default_precision,
503 };
504
505 if precision > default_precision {
506 bail!(
507 "SECOND precision must be (0, 6), have SECOND({})",
508 precision
509 )
510 }
511
512 let precision = match u32::try_from(precision) {
513 Ok(p) => p,
514 Err(_) => bail!(
515 "SECOND precision must be (0, 6), have SECOND({})",
516 precision
517 ),
518 };
519 let remainder = self.micros % 10_i64.pow(6 - precision);
521 self.micros -= remainder;
522 if matches!(round_behavior, RoundBehavior::Nearest)
524 && u64::from(precision) != default_precision
525 {
526 let rounding_digit = remainder / 10_i64.pow(5 - precision);
527 let micros = if rounding_digit > 4 {
528 self.micros.checked_add(10_i64.pow(6 - precision))
529 } else if rounding_digit < -4 {
530 self.micros.checked_sub(10_i64.pow(6 - precision))
531 } else {
532 Some(self.micros)
533 };
534 let Some(micros) = micros else {
535 bail!("interval field value out of range: \"{self}\"");
536 };
537 self.micros = micros;
538 }
539 }
540 Day => {
541 self.micros = 0;
542 }
543 Hour | Minute | Milliseconds | Microseconds => {
544 self.micros -= self.micros % f.micros_multiplier();
545 }
546 }
547 Ok(())
548 }
549
550 pub fn as_time_interval(&self) -> Self {
552 Self::new(0, 0, self.micros)
553 }
554
555 pub fn is_negative(&self) -> bool {
557 self.as_microseconds() < 0
558 }
559
560 pub fn convert_date_time_unit<T>(
567 source: DateTimeField,
568 dest: DateTimeField,
569 val: T,
570 ) -> Option<T>
571 where
572 T: From<u16> + CheckedMul + std::ops::DivAssign,
573 {
574 if source < dest {
575 Self::convert_date_time_unit_increasing(source, dest, val)
576 } else if source > dest {
577 Self::convert_date_time_unit_decreasing(source, dest, val)
578 } else {
579 Some(val)
580 }
581 }
582
583 fn convert_date_time_unit_increasing<T>(
584 source: DateTimeField,
585 dest: DateTimeField,
586 val: T,
587 ) -> Option<T>
588 where
589 T: From<u16> + std::ops::DivAssign,
590 {
591 let mut cur_unit = source;
592 let mut res = val;
593 while cur_unit < dest {
594 let divisor: T = match cur_unit {
595 DateTimeField::Millennium => 1.into(),
596 DateTimeField::Century => Self::CENTURY_PER_MILLENNIUM.into(),
597 DateTimeField::Decade => Self::DECADE_PER_CENTURY.into(),
598 DateTimeField::Year => Self::YEAR_PER_DECADE.into(),
599 DateTimeField::Month => Self::MONTH_PER_YEAR.into(),
600 DateTimeField::Day => Self::DAY_PER_MONTH.into(),
601 DateTimeField::Hour => Self::HOUR_PER_DAY.into(),
602 DateTimeField::Minute => Self::MINUTE_PER_HOUR.into(),
603 DateTimeField::Second => Self::SECOND_PER_MINUTE.into(),
604 DateTimeField::Milliseconds => Self::MILLISECOND_PER_SECOND.into(),
605 DateTimeField::Microseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
606 };
607 res /= divisor;
608 cur_unit = cur_unit.next_largest();
609 }
610
611 Some(res)
612 }
613
614 fn convert_date_time_unit_decreasing<T>(
615 source: DateTimeField,
616 dest: DateTimeField,
617 val: T,
618 ) -> Option<T>
619 where
620 T: From<u16> + CheckedMul,
621 {
622 let mut cur_unit = source;
623 let mut res = val;
624 while cur_unit > dest {
625 let multiplier: T = match cur_unit {
626 DateTimeField::Millennium => Self::CENTURY_PER_MILLENNIUM.into(),
627 DateTimeField::Century => Self::DECADE_PER_CENTURY.into(),
628 DateTimeField::Decade => Self::YEAR_PER_DECADE.into(),
629 DateTimeField::Year => Self::MONTH_PER_YEAR.into(),
630 DateTimeField::Month => Self::DAY_PER_MONTH.into(),
631 DateTimeField::Day => Self::HOUR_PER_DAY.into(),
632 DateTimeField::Hour => Self::MINUTE_PER_HOUR.into(),
633 DateTimeField::Minute => Self::SECOND_PER_MINUTE.into(),
634 DateTimeField::Second => Self::MILLISECOND_PER_SECOND.into(),
635 DateTimeField::Milliseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
636 DateTimeField::Microseconds => 1.into(),
637 };
638 res = match res.checked_mul(&multiplier) {
639 Some(r) => r,
640 None => return None,
641 };
642 cur_unit = cur_unit.next_smallest();
643 }
644
645 Some(res)
646 }
647
648 pub fn justify_days(&self) -> Result<Self, anyhow::Error> {
650 let days_per_month = i32::from(Self::DAY_PER_MONTH);
651 let (mut months, mut days) = Self::justify_days_inner(self.months, self.days)?;
652 if months > 0 && days < 0 {
653 days += days_per_month;
654 months -= 1;
655 } else if months < 0 && days > 0 {
656 days -= days_per_month;
657 months += 1;
658 }
659
660 Ok(Self::new(months, days, self.micros))
661 }
662
663 fn justify_days_inner(months: i32, days: i32) -> Result<(i32, i32), anyhow::Error> {
664 let days_per_month = i32::from(Self::DAY_PER_MONTH);
665 let whole_month = days / days_per_month;
666 let days = days - whole_month * days_per_month;
667
668 let months = months
669 .checked_add(whole_month)
670 .ok_or_else(|| anyhow!(&*MONTH_OVERFLOW_ERROR))?;
671
672 Ok((months, days))
673 }
674
675 pub fn justify_hours(&self) -> Result<Self, anyhow::Error> {
677 let (mut days, mut micros) = Self::justify_hours_inner(self.days, self.micros)?;
678 if days > 0 && micros < 0 {
679 micros += &*USECS_PER_DAY;
680 days -= 1;
681 } else if days < 0 && micros > 0 {
682 micros -= &*USECS_PER_DAY;
683 days += 1;
684 }
685
686 Ok(Self::new(self.months, days, micros))
687 }
688
689 fn justify_hours_inner(days: i32, micros: i64) -> Result<(i32, i64), anyhow::Error> {
690 let days = i32::try_from(micros / &*USECS_PER_DAY)
691 .ok()
692 .and_then(|d| days.checked_add(d))
693 .ok_or_else(|| anyhow!(&*DAY_OVERFLOW_ERROR))?;
694 let micros = micros % &*USECS_PER_DAY;
695
696 Ok((days, micros))
697 }
698
699 pub fn justify_interval(&self) -> Result<Self, anyhow::Error> {
703 let days_per_month = i32::from(Self::DAY_PER_MONTH);
704 let mut months = self.months;
705 let mut days = self.days;
706 let micros = self.micros;
707 if (days > 0 && micros > 0) || (days < 0 && micros < 0) {
710 let (m, d) = Self::justify_days_inner(self.months, self.days)?;
711 months = m;
712 days = d;
713 }
714 let (days, mut micros) = Self::justify_hours_inner(days, micros)?;
715 let (mut months, mut days) = Self::justify_days_inner(months, days)?;
716
717 if months > 0 && (days < 0 || (days == 0 && micros < 0)) {
718 days += days_per_month;
719 months -= 1;
720 } else if months < 0 && (days > 0 || (days == 0 && micros > 0)) {
721 days -= days_per_month;
722 months += 1;
723 }
724
725 if days > 0 && micros < 0 {
726 micros += &*USECS_PER_DAY;
727 days -= 1;
728 } else if days < 0 && micros > 0 {
729 micros -= &*USECS_PER_DAY;
730 days += 1;
731 }
732
733 Ok(Self::new(months, days, micros))
734 }
735}
736
737impl fmt::Display for Interval {
745 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
746 let neg_months = self.months < 0;
747 let years = (self.months / 12).abs();
748 let months = (self.months % 12).abs();
749
750 let neg_days = self.days < 0;
751 let days = i64::from(self.days).abs();
752
753 let mut nanos = self.nanoseconds().abs();
754 let mut secs = (self.micros / 1_000_000).abs();
755
756 let sec_per_hr = 60 * 60;
757 let hours = secs / sec_per_hr;
758 secs %= sec_per_hr;
759
760 let sec_per_min = 60;
761 let minutes = secs / sec_per_min;
762 secs %= sec_per_min;
763
764 if years > 0 {
765 if neg_months {
766 f.write_char('-')?;
767 }
768 write!(f, "{} year", years)?;
769 if years > 1 || neg_months {
770 f.write_char('s')?;
771 }
772 }
773
774 if months > 0 {
775 if years != 0 {
776 f.write_char(' ')?;
777 }
778 if neg_months {
779 f.write_char('-')?;
780 }
781 write!(f, "{} mon", months)?;
782 if months > 1 || neg_months {
783 f.write_char('s')?;
784 }
785 }
786
787 if days != 0 {
788 if years > 0 || months > 0 {
789 f.write_char(' ')?;
790 }
791 if neg_months && !neg_days {
792 f.write_char('+')?;
793 }
794 write!(f, "{} day", self.days)?;
795 if self.days != 1 {
796 f.write_char('s')?;
797 }
798 }
799
800 let non_zero_hmsn = hours > 0 || minutes > 0 || secs > 0 || nanos > 0;
801
802 if (years == 0 && months == 0 && days == 0) || non_zero_hmsn {
803 if years > 0 || months > 0 || days > 0 {
804 f.write_char(' ')?;
805 }
806 if self.micros < 0 && non_zero_hmsn {
807 f.write_char('-')?;
808 } else if neg_days || (days == 0 && neg_months) {
809 f.write_char('+')?;
810 }
811 write!(f, "{:02}:{:02}:{:02}", hours, minutes, secs)?;
812 if nanos > 0 {
813 let mut width = 9;
814 while nanos % 10 == 0 {
815 width -= 1;
816 nanos /= 10;
817 }
818 write!(f, ".{:0width$}", nanos, width = width)?;
819 }
820 }
821
822 Ok(())
823 }
824}
825
826#[cfg(any(test, feature = "proptest"))]
827impl Arbitrary for Interval {
828 type Strategy = BoxedStrategy<Self>;
829 type Parameters = ();
830
831 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
832 (
833 any::<i32>(),
834 any::<i32>(),
835 ((((i64::from(i32::MIN) * 60) - 59) * 60) * 1_000_000 - 59_999_999
836 ..(((i64::from(i32::MAX) * 60) + 59) * 60) * 1_000_000 + 59_999_999),
837 )
838 .prop_map(|(months, days, micros)| Interval {
839 months,
840 days,
841 micros,
842 })
843 .boxed()
844 }
845}
846
847#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
851pub struct PackedInterval([u8; Self::SIZE]);
852
853#[allow(clippy::as_conversions)]
857impl FixedSizeCodec<Interval> for PackedInterval {
858 const SIZE: usize = 16;
859
860 fn as_bytes(&self) -> &[u8] {
861 &self.0[..]
862 }
863
864 fn from_bytes(slice: &[u8]) -> Result<Self, String> {
865 let buf: [u8; Self::SIZE] = slice.try_into().map_err(|_| {
866 format!(
867 "size for PackedInterval is {} bytes, got {}",
868 Self::SIZE,
869 slice.len()
870 )
871 })?;
872 Ok(PackedInterval(buf))
873 }
874
875 #[inline]
876 fn from_value(value: Interval) -> Self {
877 let mut buf = [0u8; 16];
878
879 let months = (value.months as u32) ^ (0x8000_0000u32);
882 let days = (value.days as u32) ^ (0x8000_0000u32);
883 let micros = (value.micros as u64) ^ (0x8000_0000_0000_0000u64);
884
885 buf[..4].copy_from_slice(&months.to_be_bytes());
886 buf[4..8].copy_from_slice(&days.to_be_bytes());
887 buf[8..].copy_from_slice(µs.to_be_bytes());
888
889 PackedInterval(buf)
890 }
891
892 #[inline]
893 fn into_value(self) -> Interval {
894 let mut months = [0; 4];
897 months.copy_from_slice(&self.0[..4]);
898 let months = u32::from_be_bytes(months) ^ 0x8000_0000u32;
899
900 let mut days = [0; 4];
901 days.copy_from_slice(&self.0[4..8]);
902 let days = u32::from_be_bytes(days) ^ 0x8000_0000u32;
903
904 let mut micros = [0; 8];
905 micros.copy_from_slice(&self.0[8..]);
906 let micros = u64::from_be_bytes(micros) ^ 0x8000_0000_0000_0000u64;
907
908 Interval {
909 months: months as i32,
910 days: days as i32,
911 micros: micros as i64,
912 }
913 }
914}
915
916#[cfg(test)]
917mod test {
918 use mz_ore::assert_ok;
919 use mz_proto::protobuf_roundtrip;
920
921 use super::*;
922 use proptest::prelude::*;
923
924 #[mz_ore::test]
925 fn interval_fmt() {
926 fn mon(mon: i32) -> String {
927 Interval {
928 months: mon,
929 ..Default::default()
930 }
931 .to_string()
932 }
933
934 assert_eq!(mon(1), "1 mon");
935 assert_eq!(mon(12), "1 year");
936 assert_eq!(mon(13), "1 year 1 mon");
937 assert_eq!(mon(24), "2 years");
938 assert_eq!(mon(25), "2 years 1 mon");
939 assert_eq!(mon(26), "2 years 2 mons");
940
941 fn dur(days: i32, micros: i64) -> String {
942 Interval::new(0, days, micros).to_string()
943 }
944 assert_eq!(&dur(2, 0), "2 days");
945 assert_eq!(&dur(2, 3 * 60 * 60 * 1_000_000), "2 days 03:00:00");
946 assert_eq!(
947 &dur(
948 2,
949 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
950 ),
951 "2 days 03:45:06"
952 );
953 assert_eq!(
954 &dur(2, (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000)),
955 "2 days 03:45:00"
956 );
957 assert_eq!(&dur(2, 6 * 1_000_000), "2 days 00:00:06");
958 assert_eq!(
959 &dur(2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
960 "2 days 00:45:06"
961 );
962 assert_eq!(
963 &dur(2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
964 "2 days 03:00:06"
965 );
966 assert_eq!(
967 &dur(
968 0,
969 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
970 ),
971 "03:45:06"
972 );
973 assert_eq!(
974 &dur(0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
975 "03:00:06"
976 );
977 assert_eq!(&dur(0, 3 * 60 * 60 * 1_000_000), "03:00:00");
978 assert_eq!(&dur(0, (45 * 60 * 1_000_000) + (6 * 1_000_000)), "00:45:06");
979 assert_eq!(&dur(0, 45 * 60 * 1_000_000), "00:45:00");
980 assert_eq!(&dur(0, 6 * 1_000_000), "00:00:06");
981
982 assert_eq!(&dur(-2, -6 * 1_000_000), "-2 days -00:00:06");
983 assert_eq!(
984 &dur(-2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
985 "-2 days -00:45:06"
986 );
987 assert_eq!(
988 &dur(-2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
989 "-2 days -03:00:06"
990 );
991 assert_eq!(
992 &dur(
993 0,
994 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
995 ),
996 "-03:45:06"
997 );
998 assert_eq!(
999 &dur(0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1000 "-03:00:06"
1001 );
1002 assert_eq!(&dur(0, -3 * 60 * 60 * 1_000_000), "-03:00:00");
1003 assert_eq!(
1004 &dur(0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1005 "-00:45:06"
1006 );
1007 assert_eq!(&dur(0, -45 * 60 * 1_000_000), "-00:45:00");
1008 assert_eq!(&dur(0, -6 * 1_000_000), "-00:00:06");
1009
1010 fn mon_dur(mon: i32, days: i32, micros: i64) -> String {
1011 Interval::new(mon, days, micros).to_string()
1012 }
1013 assert_eq!(&mon_dur(1, 2, 6 * 1_000_000), "1 mon 2 days 00:00:06");
1014 assert_eq!(
1015 &mon_dur(1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1016 "1 mon 2 days 00:45:06"
1017 );
1018 assert_eq!(
1019 &mon_dur(1, 2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1020 "1 mon 2 days 03:00:06"
1021 );
1022 assert_eq!(
1023 &mon_dur(
1024 26,
1025 0,
1026 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1027 ),
1028 "2 years 2 mons 03:45:06"
1029 );
1030 assert_eq!(
1031 &mon_dur(26, 0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1032 "2 years 2 mons 03:00:06"
1033 );
1034 assert_eq!(
1035 &mon_dur(26, 0, 3 * 60 * 60 * 1_000_000),
1036 "2 years 2 mons 03:00:00"
1037 );
1038 assert_eq!(
1039 &mon_dur(26, 0, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1040 "2 years 2 mons 00:45:06"
1041 );
1042 assert_eq!(
1043 &mon_dur(26, 0, 45 * 60 * 1_000_000),
1044 "2 years 2 mons 00:45:00"
1045 );
1046 assert_eq!(&mon_dur(26, 0, 6 * 1_000_000), "2 years 2 mons 00:00:06");
1047
1048 assert_eq!(
1049 &mon_dur(26, -2, -6 * 1_000_000),
1050 "2 years 2 mons -2 days -00:00:06"
1051 );
1052 assert_eq!(
1053 &mon_dur(26, -2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1054 "2 years 2 mons -2 days -00:45:06"
1055 );
1056 assert_eq!(
1057 &mon_dur(26, -2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1058 "2 years 2 mons -2 days -03:00:06"
1059 );
1060 assert_eq!(
1061 &mon_dur(
1062 26,
1063 0,
1064 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1065 ),
1066 "2 years 2 mons -03:45:06"
1067 );
1068 assert_eq!(
1069 &mon_dur(26, 0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1070 "2 years 2 mons -03:00:06"
1071 );
1072 assert_eq!(
1073 &mon_dur(26, 0, -3 * 60 * 60 * 1_000_000),
1074 "2 years 2 mons -03:00:00"
1075 );
1076 assert_eq!(
1077 &mon_dur(26, 0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1078 "2 years 2 mons -00:45:06"
1079 );
1080 assert_eq!(
1081 &mon_dur(26, 0, -45 * 60 * 1_000_000),
1082 "2 years 2 mons -00:45:00"
1083 );
1084 assert_eq!(&mon_dur(26, 0, -6 * 1_000_000), "2 years 2 mons -00:00:06");
1085
1086 assert_eq!(&mon_dur(-1, 2, 6 * 1_000_000), "-1 mons +2 days 00:00:06");
1087 assert_eq!(
1088 &mon_dur(-1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1089 "-1 mons +2 days 00:45:06"
1090 );
1091 assert_eq!(
1092 &mon_dur(-1, 2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1093 "-1 mons +2 days 03:00:06"
1094 );
1095 assert_eq!(
1096 &mon_dur(
1097 -26,
1098 0,
1099 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1100 ),
1101 "-2 years -2 mons +03:45:06"
1102 );
1103 assert_eq!(
1104 &mon_dur(-26, 0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1105 "-2 years -2 mons +03:00:06"
1106 );
1107 assert_eq!(
1108 &mon_dur(-26, 0, 3 * 60 * 60 * 1_000_000),
1109 "-2 years -2 mons +03:00:00"
1110 );
1111 assert_eq!(
1112 &mon_dur(-26, 0, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1113 "-2 years -2 mons +00:45:06"
1114 );
1115 assert_eq!(
1116 &mon_dur(-26, 0, 45 * 60 * 1_000_000),
1117 "-2 years -2 mons +00:45:00"
1118 );
1119 assert_eq!(
1120 &mon_dur(-26, 0, 6 * 1_000_000),
1121 "-2 years -2 mons +00:00:06"
1122 );
1123
1124 assert_eq!(
1125 &mon_dur(-26, -2, -6 * 1_000_000),
1126 "-2 years -2 mons -2 days -00:00:06"
1127 );
1128 assert_eq!(
1129 &mon_dur(-26, -2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1130 "-2 years -2 mons -2 days -00:45:06"
1131 );
1132 assert_eq!(
1133 &mon_dur(-26, -2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1134 "-2 years -2 mons -2 days -03:00:06"
1135 );
1136 assert_eq!(
1137 &mon_dur(
1138 -26,
1139 0,
1140 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1141 ),
1142 "-2 years -2 mons -03:45:06"
1143 );
1144 assert_eq!(
1145 &mon_dur(-26, 0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1146 "-2 years -2 mons -03:00:06"
1147 );
1148 assert_eq!(
1149 &mon_dur(-26, 0, -3 * 60 * 60 * 1_000_000),
1150 "-2 years -2 mons -03:00:00"
1151 );
1152 assert_eq!(
1153 &mon_dur(-26, 0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1154 "-2 years -2 mons -00:45:06"
1155 );
1156 assert_eq!(
1157 &mon_dur(-26, 0, -45 * 60 * 1_000_000),
1158 "-2 years -2 mons -00:45:00"
1159 );
1160 assert_eq!(
1161 &mon_dur(-26, 0, -6 * 1_000_000),
1162 "-2 years -2 mons -00:00:06"
1163 );
1164 }
1165
1166 #[mz_ore::test]
1167 fn test_interval_value_truncate_low_fields() {
1168 use DateTimeField::*;
1169
1170 let mut test_cases = [
1171 (
1172 Year,
1173 None,
1174 (
1175 321,
1176 7,
1177 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1178 ),
1179 (26 * 12, 0, 0),
1180 ),
1181 (
1182 Month,
1183 None,
1184 (
1185 321,
1186 7,
1187 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1188 ),
1189 (321, 0, 0),
1190 ),
1191 (
1192 Day,
1193 None,
1194 (
1195 321,
1196 7,
1197 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1198 ),
1199 (321, 7, 0),
1200 ),
1201 (
1202 Hour,
1203 None,
1204 (
1205 321,
1206 7,
1207 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1208 ),
1209 (321, 7, 13 * 60 * 60 * 1_000_000),
1210 ),
1211 (
1212 Minute,
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 (321, 7, (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000)),
1220 ),
1221 (
1222 Second,
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 (
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 (
1236 Second,
1237 Some(1),
1238 (
1239 321,
1240 7,
1241 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1242 ),
1243 (
1244 321,
1245 7,
1246 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 300_000,
1247 ),
1248 ),
1249 (
1250 Second,
1251 Some(0),
1252 (
1253 321,
1254 7,
1255 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1256 ),
1257 (
1258 321,
1259 7,
1260 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1261 ),
1262 ),
1263 ];
1264
1265 for test in test_cases.iter_mut() {
1266 let mut i = Interval::new((test.2).0, (test.2).1, (test.2).2);
1267 let j = Interval::new((test.3).0, (test.3).1, (test.3).2);
1268
1269 i.truncate_low_fields(test.0, test.1, RoundBehavior::Nearest)
1270 .unwrap();
1271
1272 if i != j {
1273 panic!(
1274 "test_interval_value_truncate_low_fields failed on {} \n actual: {:?} \n expected: {:?}",
1275 test.0, i, j
1276 );
1277 }
1278 }
1279 }
1280
1281 #[mz_ore::test]
1282 fn test_convert_date_time_unit() {
1283 assert_eq!(
1284 Some(1_123_200_000_000),
1285 Interval::convert_date_time_unit(
1286 DateTimeField::Day,
1287 DateTimeField::Microseconds,
1288 13i64
1289 )
1290 );
1291
1292 assert_eq!(
1293 Some(3_558_399_705),
1294 Interval::convert_date_time_unit(
1295 DateTimeField::Milliseconds,
1296 DateTimeField::Month,
1297 i64::MAX
1298 )
1299 );
1300
1301 assert_eq!(
1302 None,
1303 Interval::convert_date_time_unit(
1304 DateTimeField::Minute,
1305 DateTimeField::Second,
1306 i32::MAX
1307 )
1308 );
1309
1310 assert_eq!(
1311 Some(1),
1312 Interval::convert_date_time_unit(DateTimeField::Day, DateTimeField::Year, 365)
1313 );
1314
1315 assert_eq!(
1317 Some(360),
1318 Interval::convert_date_time_unit(DateTimeField::Year, DateTimeField::Day, 1)
1319 );
1320 }
1321
1322 #[mz_ore::test]
1323 fn proptest_packed_interval_roundtrips() {
1324 fn roundtrip_interval(og: Interval) {
1325 let packed = PackedInterval::from_value(og);
1326 let rnd = packed.into_value();
1327
1328 assert_eq!(og, rnd);
1329 }
1330
1331 proptest!(|(interval in any::<Interval>())| {
1332 roundtrip_interval(interval);
1333 });
1334 }
1335
1336 #[mz_ore::test]
1337 #[cfg_attr(miri, ignore)] fn proptest_packed_interval_sorts() {
1339 fn sort_intervals(mut og: Vec<Interval>) {
1340 let mut packed: Vec<_> = og.iter().copied().map(PackedInterval::from_value).collect();
1341
1342 og.sort();
1343 packed.sort();
1344
1345 let rnd: Vec<_> = packed.into_iter().map(PackedInterval::into_value).collect();
1346
1347 assert_eq!(og, rnd);
1348 }
1349
1350 proptest!(|(interval in any::<Vec<Interval>>())| {
1351 sort_intervals(interval);
1352 });
1353 }
1354
1355 proptest! {
1361 #[mz_ore::test]
1362 fn interval_protobuf_roundtrip(expect in any::<Interval>()) {
1363 let actual = protobuf_roundtrip::<_, ProtoInterval>(&expect);
1364 assert_ok!(actual);
1365 assert_eq!(actual.unwrap(), expect);
1366 }
1367 }
1368}