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;
20use proptest::prelude::{Arbitrary, BoxedStrategy, Strategy, any};
21use serde::{Deserialize, Serialize};
22
23use crate::adt::datetime::DateTimeField;
24use crate::adt::numeric::{DecimalLike, Numeric};
25
26include!(concat!(env!("OUT_DIR"), "/mz_repr.adt.interval.rs"));
27
28#[derive(
32 Debug,
33 Clone,
34 Copy,
35 PartialEq,
36 Eq,
37 PartialOrd,
38 Ord,
39 Serialize,
40 Hash,
41 Deserialize
42)]
43pub struct Interval {
44 pub months: i32,
46 pub days: i32,
50 pub micros: i64,
55}
56
57impl Default for Interval {
58 fn default() -> Self {
59 Self {
60 months: 0,
61 days: 0,
62 micros: 0,
63 }
64 }
65}
66
67impl RustType<ProtoInterval> for Interval {
68 fn into_proto(&self) -> ProtoInterval {
69 ProtoInterval {
70 months: self.months,
71 days: self.days,
72 micros: self.micros,
73 }
74 }
75
76 fn from_proto(proto: ProtoInterval) -> Result<Self, TryFromProtoError> {
77 Ok(Interval {
78 months: proto.months,
79 days: proto.days,
80 micros: proto.micros,
81 })
82 }
83}
84
85impl num_traits::ops::checked::CheckedNeg for Interval {
86 fn checked_neg(&self) -> Option<Self> {
87 if let (Some(months), Some(days), Some(micros)) = (
88 self.months.checked_neg(),
89 self.days.checked_neg(),
90 self.micros.checked_neg(),
91 ) {
92 Some(Self::new(months, days, micros))
93 } else {
94 None
95 }
96 }
97}
98
99impl std::str::FromStr for Interval {
100 type Err = anyhow::Error;
101
102 fn from_str(s: &str) -> Result<Self, Self::Err> {
103 crate::strconv::parse_interval(s).map_err(|e| anyhow!(e))
104 }
105}
106
107static MONTH_OVERFLOW_ERROR: LazyLock<String> = LazyLock::new(|| {
108 format!(
109 "Overflows maximum months; cannot exceed {}/{} microseconds",
110 i32::MAX,
111 i32::MIN,
112 )
113});
114static DAY_OVERFLOW_ERROR: LazyLock<String> = LazyLock::new(|| {
115 format!(
116 "Overflows maximum days; cannot exceed {}/{} microseconds",
117 i32::MAX,
118 i32::MIN,
119 )
120});
121pub static USECS_PER_DAY: LazyLock<i64> = LazyLock::new(|| {
122 Interval::convert_date_time_unit(DateTimeField::Day, DateTimeField::Microseconds, 1i64).unwrap()
123});
124
125#[derive(Debug, Clone)]
126pub enum RoundBehavior {
127 Truncate,
128 Nearest,
129}
130
131impl Interval {
132 pub const CENTURY_PER_MILLENNIUM: u16 = 10;
133 pub const DECADE_PER_CENTURY: u16 = 10;
134 pub const YEAR_PER_DECADE: u16 = 10;
135 pub const MONTH_PER_YEAR: u16 = 12;
136 pub const DAY_PER_MONTH: u16 = 30;
138 pub const HOUR_PER_DAY: u16 = 24;
140 pub const MINUTE_PER_HOUR: u16 = 60;
141 pub const SECOND_PER_MINUTE: u16 = 60;
142 pub const MILLISECOND_PER_SECOND: u16 = 1_000;
143 pub const MICROSECOND_PER_MILLISECOND: u16 = 1_000;
144 pub const NANOSECOND_PER_MICROSECOND: u16 = 1_000;
145 pub const EPOCH_DAYS_PER_YEAR: f64 = 365.25;
151
152 pub const fn new(months: i32, days: i32, micros: i64) -> Interval {
154 Interval {
155 months,
156 days,
157 micros,
158 }
159 }
160
161 pub fn from_duration(duration: &Duration) -> Result<Interval, anyhow::Error> {
166 if duration.subsec_nanos() % 1000 != 0 {
167 return Err(anyhow!(
168 "cannot convert Duration to Interval due to fractional microseconds"
169 ));
170 }
171 Ok(Interval {
172 months: 0,
173 days: 0,
174 micros: duration.as_micros().try_into()?,
175 })
176 }
177
178 pub fn from_chrono_duration(duration: chrono::Duration) -> Result<Self, anyhow::Error> {
181 let Some(micros) = duration.num_microseconds() else {
182 bail!("cannot convert Duration to Interval due to overflowed microseconds");
183 };
184 Ok(Self {
185 months: 0,
186 days: 0,
187 micros,
188 })
189 }
190
191 pub fn checked_add(&self, other: &Self) -> Option<Self> {
192 let months = match self.months.checked_add(other.months) {
193 Some(m) => m,
194 None => return None,
195 };
196 let days = match self.days.checked_add(other.days) {
197 Some(d) => d,
198 None => return None,
199 };
200 let micros = match self.micros.checked_add(other.micros) {
201 Some(us) => us,
202 None => return None,
203 };
204
205 Some(Self::new(months, days, micros))
206 }
207
208 pub fn checked_mul(&self, other: f64) -> Option<Self> {
209 self.checked_op(other, |f1, f2| f1 * f2)
210 }
211
212 pub fn checked_div(&self, other: f64) -> Option<Self> {
213 self.checked_op(other, |f1, f2| f1 / f2)
214 }
215
216 #[allow(clippy::as_conversions)]
219 fn checked_op<F1>(&self, other: f64, op: F1) -> Option<Self>
220 where
221 F1: Fn(f64, f64) -> f64,
222 {
223 let months = op(f64::from(self.months), other);
224 if months.is_nan()
225 || months.is_infinite()
226 || months < i32::MIN.into()
227 || months > i32::MAX.into()
228 {
229 return None;
230 }
231
232 let days =
233 op(f64::from(self.days), other) + months.fract() * f64::from(Self::DAY_PER_MONTH);
234 if days.is_nan() || days.is_infinite() || days < i32::MIN.into() || days > i32::MAX.into() {
235 return None;
236 }
237
238 let micros = op(self.micros as f64, other)
239 + days.fract()
240 * f64::from(Self::HOUR_PER_DAY)
241 * f64::from(Self::MINUTE_PER_HOUR)
242 * f64::from(Self::SECOND_PER_MINUTE)
243 * f64::from(Self::MILLISECOND_PER_SECOND)
244 * f64::from(Self::MICROSECOND_PER_MILLISECOND);
245
246 if micros.is_nan()
247 || micros.is_infinite()
248 || Numeric::from(micros) < Numeric::from(i64::MIN)
249 || Numeric::from(micros) > Numeric::from(i64::MAX)
250 {
251 return None;
252 }
253
254 Some(Self::new(months as i32, days as i32, micros as i64))
255 }
256
257 pub fn millennia(&self) -> i32 {
262 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Millennium, self.months)
263 .unwrap()
264 }
265
266 pub fn centuries(&self) -> i32 {
271 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Century, self.months)
272 .unwrap()
273 }
274
275 pub fn decades(&self) -> i32 {
280 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Decade, self.months)
281 .unwrap()
282 }
283
284 pub fn years(&self) -> i32 {
289 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Year, self.months)
290 .unwrap()
291 }
292
293 pub fn quarters(&self) -> i32 {
299 self.months() / 3 + 1
300 }
301
302 pub fn months(&self) -> i32 {
308 self.months % i32::from(Self::MONTH_PER_YEAR)
309 }
310
311 pub fn days(&self) -> i64 {
317 self.days.into()
318 }
319
320 pub fn hours(&self) -> i64 {
326 Self::convert_date_time_unit(
327 DateTimeField::Microseconds,
328 DateTimeField::Hour,
329 self.micros,
330 )
331 .unwrap()
332 % i64::from(Self::HOUR_PER_DAY)
333 }
334
335 pub fn minutes(&self) -> i64 {
341 Self::convert_date_time_unit(
342 DateTimeField::Microseconds,
343 DateTimeField::Minute,
344 self.micros,
345 )
346 .unwrap()
347 % i64::from(Self::MINUTE_PER_HOUR)
348 }
349
350 pub fn seconds<T>(&self) -> T
355 where
356 T: DecimalLike,
357 {
358 T::lossy_from(self.micros % 60_000_000) / T::from(1e6)
359 }
360
361 pub fn milliseconds<T>(&self) -> T
366 where
367 T: DecimalLike,
368 {
369 T::lossy_from(self.micros % 60_000_000) / T::from(1e3)
370 }
371
372 pub fn microseconds<T>(&self) -> T
377 where
378 T: DecimalLike,
379 {
380 T::lossy_from(self.micros % 60_000_000)
381 }
382
383 pub fn nanoseconds(&self) -> i32 {
385 (self.micros % 1_000_000 * 1_000).try_into().unwrap()
386 }
387
388 pub fn as_epoch_seconds<T>(&self) -> T
392 where
393 T: DecimalLike,
394 {
395 let days = T::from(self.years()) * T::from(Self::EPOCH_DAYS_PER_YEAR)
396 + T::from(self.months()) * T::from(Self::DAY_PER_MONTH)
397 + T::from(self.days);
398 let seconds = days
399 * T::from(Self::HOUR_PER_DAY)
400 * T::from(Self::MINUTE_PER_HOUR)
401 * T::from(Self::SECOND_PER_MINUTE);
402
403 seconds
404 + T::lossy_from(self.micros)
405 / (T::from(Self::MICROSECOND_PER_MILLISECOND)
406 * T::from(Self::MILLISECOND_PER_SECOND))
407 }
408
409 pub fn as_microseconds(&self) -> i128 {
411 Self::convert_date_time_unit(
414 DateTimeField::Month,
415 DateTimeField::Microseconds,
416 i128::from(self.months),
417 ).unwrap() +
418 Self::convert_date_time_unit(
421 DateTimeField::Day,
422 DateTimeField::Microseconds,
423 i128::from(self.days),
424 ).unwrap() +
425 i128::from(self.micros)
426 }
427
428 pub fn as_milliseconds(&self) -> i128 {
430 self.as_microseconds() / 1000
431 }
432
433 pub fn duration_as_chrono(&self) -> chrono::Duration {
435 use chrono::Duration;
436 Duration::try_days(self.days.into()).unwrap() + Duration::microseconds(self.micros)
437 }
438
439 pub fn duration(&self) -> Result<Duration, anyhow::Error> {
440 if self.months != 0 {
441 bail!("cannot convert interval with months to duration");
442 }
443 if self.is_negative() {
444 bail!("cannot convert negative interval to duration");
445 }
446 let micros: u64 = u64::try_from(self.as_microseconds())?;
447 Ok(Duration::from_micros(micros))
448 }
449
450 pub fn truncate_high_fields(&mut self, f: DateTimeField) {
452 match f {
453 DateTimeField::Year => {}
454 DateTimeField::Month => self.months %= 12,
455 DateTimeField::Day => self.months = 0,
456 DateTimeField::Hour | DateTimeField::Minute | DateTimeField::Second => {
457 self.months = 0;
458 self.days = 0;
459 self.micros %= f.next_largest().micros_multiplier()
460 }
461 DateTimeField::Millennium
462 | DateTimeField::Century
463 | DateTimeField::Decade
464 | DateTimeField::Milliseconds
465 | DateTimeField::Microseconds => {
466 unreachable!("Cannot truncate interval by {f}");
467 }
468 }
469 }
470
471 pub fn truncate_low_fields(
480 &mut self,
481 f: DateTimeField,
482 fsec_max_precision: Option<u64>,
483 round_behavior: RoundBehavior,
484 ) -> Result<(), anyhow::Error> {
485 use DateTimeField::*;
486 match f {
487 Millennium => {
488 self.months -= self.months % (12 * 1000);
489 self.days = 0;
490 self.micros = 0;
491 }
492 Century => {
493 self.months -= self.months % (12 * 100);
494 self.days = 0;
495 self.micros = 0;
496 }
497 Decade => {
498 self.months -= self.months % (12 * 10);
499 self.days = 0;
500 self.micros = 0;
501 }
502 Year => {
503 self.months -= self.months % 12;
504 self.days = 0;
505 self.micros = 0;
506 }
507 Month => {
508 self.days = 0;
509 self.micros = 0;
510 }
511 Second => {
513 let default_precision = 6;
514 let precision = match fsec_max_precision {
515 Some(p) => p,
516 None => default_precision,
517 };
518
519 if precision > default_precision {
520 bail!(
521 "SECOND precision must be (0, 6), have SECOND({})",
522 precision
523 )
524 }
525
526 let precision = match u32::try_from(precision) {
527 Ok(p) => p,
528 Err(_) => bail!(
529 "SECOND precision must be (0, 6), have SECOND({})",
530 precision
531 ),
532 };
533 let remainder = self.micros % 10_i64.pow(6 - precision);
535 self.micros -= remainder;
536 if matches!(round_behavior, RoundBehavior::Nearest)
538 && u64::from(precision) != default_precision
539 {
540 let rounding_digit = remainder / 10_i64.pow(5 - precision);
541 let micros = if rounding_digit > 4 {
542 self.micros.checked_add(10_i64.pow(6 - precision))
543 } else if rounding_digit < -4 {
544 self.micros.checked_sub(10_i64.pow(6 - precision))
545 } else {
546 Some(self.micros)
547 };
548 let Some(micros) = micros else {
549 bail!("interval field value out of range: \"{self}\"");
550 };
551 self.micros = micros;
552 }
553 }
554 Day => {
555 self.micros = 0;
556 }
557 Hour | Minute | Milliseconds | Microseconds => {
558 self.micros -= self.micros % f.micros_multiplier();
559 }
560 }
561 Ok(())
562 }
563
564 pub fn as_time_interval(&self) -> Self {
566 Self::new(0, 0, self.micros)
567 }
568
569 pub fn is_negative(&self) -> bool {
571 self.as_microseconds() < 0
572 }
573
574 pub fn convert_date_time_unit<T>(
581 source: DateTimeField,
582 dest: DateTimeField,
583 val: T,
584 ) -> Option<T>
585 where
586 T: From<u16> + CheckedMul + std::ops::DivAssign,
587 {
588 if source < dest {
589 Self::convert_date_time_unit_increasing(source, dest, val)
590 } else if source > dest {
591 Self::convert_date_time_unit_decreasing(source, dest, val)
592 } else {
593 Some(val)
594 }
595 }
596
597 fn convert_date_time_unit_increasing<T>(
598 source: DateTimeField,
599 dest: DateTimeField,
600 val: T,
601 ) -> Option<T>
602 where
603 T: From<u16> + std::ops::DivAssign,
604 {
605 let mut cur_unit = source;
606 let mut res = val;
607 while cur_unit < dest {
608 let divisor: T = match cur_unit {
609 DateTimeField::Millennium => 1.into(),
610 DateTimeField::Century => Self::CENTURY_PER_MILLENNIUM.into(),
611 DateTimeField::Decade => Self::DECADE_PER_CENTURY.into(),
612 DateTimeField::Year => Self::YEAR_PER_DECADE.into(),
613 DateTimeField::Month => Self::MONTH_PER_YEAR.into(),
614 DateTimeField::Day => Self::DAY_PER_MONTH.into(),
615 DateTimeField::Hour => Self::HOUR_PER_DAY.into(),
616 DateTimeField::Minute => Self::MINUTE_PER_HOUR.into(),
617 DateTimeField::Second => Self::SECOND_PER_MINUTE.into(),
618 DateTimeField::Milliseconds => Self::MILLISECOND_PER_SECOND.into(),
619 DateTimeField::Microseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
620 };
621 res /= divisor;
622 cur_unit = cur_unit.next_largest();
623 }
624
625 Some(res)
626 }
627
628 fn convert_date_time_unit_decreasing<T>(
629 source: DateTimeField,
630 dest: DateTimeField,
631 val: T,
632 ) -> Option<T>
633 where
634 T: From<u16> + CheckedMul,
635 {
636 let mut cur_unit = source;
637 let mut res = val;
638 while cur_unit > dest {
639 let multiplier: T = match cur_unit {
640 DateTimeField::Millennium => Self::CENTURY_PER_MILLENNIUM.into(),
641 DateTimeField::Century => Self::DECADE_PER_CENTURY.into(),
642 DateTimeField::Decade => Self::YEAR_PER_DECADE.into(),
643 DateTimeField::Year => Self::MONTH_PER_YEAR.into(),
644 DateTimeField::Month => Self::DAY_PER_MONTH.into(),
645 DateTimeField::Day => Self::HOUR_PER_DAY.into(),
646 DateTimeField::Hour => Self::MINUTE_PER_HOUR.into(),
647 DateTimeField::Minute => Self::SECOND_PER_MINUTE.into(),
648 DateTimeField::Second => Self::MILLISECOND_PER_SECOND.into(),
649 DateTimeField::Milliseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
650 DateTimeField::Microseconds => 1.into(),
651 };
652 res = match res.checked_mul(&multiplier) {
653 Some(r) => r,
654 None => return None,
655 };
656 cur_unit = cur_unit.next_smallest();
657 }
658
659 Some(res)
660 }
661
662 pub fn justify_days(&self) -> Result<Self, anyhow::Error> {
664 let days_per_month = i32::from(Self::DAY_PER_MONTH);
665 let (mut months, mut days) = Self::justify_days_inner(self.months, self.days)?;
666 if months > 0 && days < 0 {
667 days += days_per_month;
668 months -= 1;
669 } else if months < 0 && days > 0 {
670 days -= days_per_month;
671 months += 1;
672 }
673
674 Ok(Self::new(months, days, self.micros))
675 }
676
677 fn justify_days_inner(months: i32, days: i32) -> Result<(i32, i32), anyhow::Error> {
678 let days_per_month = i32::from(Self::DAY_PER_MONTH);
679 let whole_month = days / days_per_month;
680 let days = days - whole_month * days_per_month;
681
682 let months = months
683 .checked_add(whole_month)
684 .ok_or_else(|| anyhow!(&*MONTH_OVERFLOW_ERROR))?;
685
686 Ok((months, days))
687 }
688
689 pub fn justify_hours(&self) -> Result<Self, anyhow::Error> {
691 let (mut days, mut micros) = Self::justify_hours_inner(self.days, self.micros)?;
692 if days > 0 && micros < 0 {
693 micros += &*USECS_PER_DAY;
694 days -= 1;
695 } else if days < 0 && micros > 0 {
696 micros -= &*USECS_PER_DAY;
697 days += 1;
698 }
699
700 Ok(Self::new(self.months, days, micros))
701 }
702
703 fn justify_hours_inner(days: i32, micros: i64) -> Result<(i32, i64), anyhow::Error> {
704 let days = i32::try_from(micros / &*USECS_PER_DAY)
705 .ok()
706 .and_then(|d| days.checked_add(d))
707 .ok_or_else(|| anyhow!(&*DAY_OVERFLOW_ERROR))?;
708 let micros = micros % &*USECS_PER_DAY;
709
710 Ok((days, micros))
711 }
712
713 pub fn justify_interval(&self) -> Result<Self, anyhow::Error> {
717 let days_per_month = i32::from(Self::DAY_PER_MONTH);
718 let mut months = self.months;
719 let mut days = self.days;
720 let micros = self.micros;
721 if (days > 0 && micros > 0) || (days < 0 && micros < 0) {
724 let (m, d) = Self::justify_days_inner(self.months, self.days)?;
725 months = m;
726 days = d;
727 }
728 let (days, mut micros) = Self::justify_hours_inner(days, micros)?;
729 let (mut months, mut days) = Self::justify_days_inner(months, days)?;
730
731 if months > 0 && (days < 0 || (days == 0 && micros < 0)) {
732 days += days_per_month;
733 months -= 1;
734 } else if months < 0 && (days > 0 || (days == 0 && micros > 0)) {
735 days -= days_per_month;
736 months += 1;
737 }
738
739 if days > 0 && micros < 0 {
740 micros += &*USECS_PER_DAY;
741 days -= 1;
742 } else if days < 0 && micros > 0 {
743 micros -= &*USECS_PER_DAY;
744 days += 1;
745 }
746
747 Ok(Self::new(months, days, micros))
748 }
749}
750
751impl fmt::Display for Interval {
759 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
760 let neg_months = self.months < 0;
761 let years = (self.months / 12).abs();
762 let months = (self.months % 12).abs();
763
764 let neg_days = self.days < 0;
765 let days = i64::from(self.days).abs();
766
767 let mut nanos = self.nanoseconds().abs();
768 let mut secs = (self.micros / 1_000_000).abs();
769
770 let sec_per_hr = 60 * 60;
771 let hours = secs / sec_per_hr;
772 secs %= sec_per_hr;
773
774 let sec_per_min = 60;
775 let minutes = secs / sec_per_min;
776 secs %= sec_per_min;
777
778 if years > 0 {
779 if neg_months {
780 f.write_char('-')?;
781 }
782 write!(f, "{} year", years)?;
783 if years > 1 || neg_months {
784 f.write_char('s')?;
785 }
786 }
787
788 if months > 0 {
789 if years != 0 {
790 f.write_char(' ')?;
791 }
792 if neg_months {
793 f.write_char('-')?;
794 }
795 write!(f, "{} month", months)?;
796 if months > 1 || neg_months {
797 f.write_char('s')?;
798 }
799 }
800
801 if days != 0 {
802 if years > 0 || months > 0 {
803 f.write_char(' ')?;
804 }
805 if neg_months && !neg_days {
806 f.write_char('+')?;
807 }
808 write!(f, "{} day", self.days)?;
809 if self.days != 1 {
810 f.write_char('s')?;
811 }
812 }
813
814 let non_zero_hmsn = hours > 0 || minutes > 0 || secs > 0 || nanos > 0;
815
816 if (years == 0 && months == 0 && days == 0) || non_zero_hmsn {
817 if years > 0 || months > 0 || days > 0 {
818 f.write_char(' ')?;
819 }
820 if self.micros < 0 && non_zero_hmsn {
821 f.write_char('-')?;
822 } else if neg_days || (days == 0 && neg_months) {
823 f.write_char('+')?;
824 }
825 write!(f, "{:02}:{:02}:{:02}", hours, minutes, secs)?;
826 if nanos > 0 {
827 let mut width = 9;
828 while nanos % 10 == 0 {
829 width -= 1;
830 nanos /= 10;
831 }
832 write!(f, ".{:0width$}", nanos, width = width)?;
833 }
834 }
835
836 Ok(())
837 }
838}
839
840impl Arbitrary for Interval {
841 type Strategy = BoxedStrategy<Self>;
842 type Parameters = ();
843
844 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
845 (
846 any::<i32>(),
847 any::<i32>(),
848 ((((i64::from(i32::MIN) * 60) - 59) * 60) * 1_000_000 - 59_999_999
849 ..(((i64::from(i32::MAX) * 60) + 59) * 60) * 1_000_000 + 59_999_999),
850 )
851 .prop_map(|(months, days, micros)| Interval {
852 months,
853 days,
854 micros,
855 })
856 .boxed()
857 }
858}
859
860#[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 super::*;
932 use proptest::prelude::*;
933
934 #[mz_ore::test]
935 fn interval_fmt() {
936 fn mon(mon: i32) -> String {
937 Interval {
938 months: mon,
939 ..Default::default()
940 }
941 .to_string()
942 }
943
944 assert_eq!(mon(1), "1 month");
945 assert_eq!(mon(12), "1 year");
946 assert_eq!(mon(13), "1 year 1 month");
947 assert_eq!(mon(24), "2 years");
948 assert_eq!(mon(25), "2 years 1 month");
949 assert_eq!(mon(26), "2 years 2 months");
950
951 fn dur(days: i32, micros: i64) -> String {
952 Interval::new(0, days, micros).to_string()
953 }
954 assert_eq!(&dur(2, 0), "2 days");
955 assert_eq!(&dur(2, 3 * 60 * 60 * 1_000_000), "2 days 03:00:00");
956 assert_eq!(
957 &dur(
958 2,
959 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
960 ),
961 "2 days 03:45:06"
962 );
963 assert_eq!(
964 &dur(2, (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000)),
965 "2 days 03:45:00"
966 );
967 assert_eq!(&dur(2, 6 * 1_000_000), "2 days 00:00:06");
968 assert_eq!(
969 &dur(2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
970 "2 days 00:45:06"
971 );
972 assert_eq!(
973 &dur(2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
974 "2 days 03:00:06"
975 );
976 assert_eq!(
977 &dur(
978 0,
979 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
980 ),
981 "03:45:06"
982 );
983 assert_eq!(
984 &dur(0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
985 "03:00:06"
986 );
987 assert_eq!(&dur(0, 3 * 60 * 60 * 1_000_000), "03:00:00");
988 assert_eq!(&dur(0, (45 * 60 * 1_000_000) + (6 * 1_000_000)), "00:45:06");
989 assert_eq!(&dur(0, 45 * 60 * 1_000_000), "00:45:00");
990 assert_eq!(&dur(0, 6 * 1_000_000), "00:00:06");
991
992 assert_eq!(&dur(-2, -6 * 1_000_000), "-2 days -00:00:06");
993 assert_eq!(
994 &dur(-2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
995 "-2 days -00:45:06"
996 );
997 assert_eq!(
998 &dur(-2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
999 "-2 days -03:00:06"
1000 );
1001 assert_eq!(
1002 &dur(
1003 0,
1004 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1005 ),
1006 "-03:45:06"
1007 );
1008 assert_eq!(
1009 &dur(0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1010 "-03:00:06"
1011 );
1012 assert_eq!(&dur(0, -3 * 60 * 60 * 1_000_000), "-03:00:00");
1013 assert_eq!(
1014 &dur(0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1015 "-00:45:06"
1016 );
1017 assert_eq!(&dur(0, -45 * 60 * 1_000_000), "-00:45:00");
1018 assert_eq!(&dur(0, -6 * 1_000_000), "-00:00:06");
1019
1020 fn mon_dur(mon: i32, days: i32, micros: i64) -> String {
1021 Interval::new(mon, days, micros).to_string()
1022 }
1023 assert_eq!(&mon_dur(1, 2, 6 * 1_000_000), "1 month 2 days 00:00:06");
1024 assert_eq!(
1025 &mon_dur(1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1026 "1 month 2 days 00:45:06"
1027 );
1028 assert_eq!(
1029 &mon_dur(1, 2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1030 "1 month 2 days 03:00:06"
1031 );
1032 assert_eq!(
1033 &mon_dur(
1034 26,
1035 0,
1036 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1037 ),
1038 "2 years 2 months 03:45:06"
1039 );
1040 assert_eq!(
1041 &mon_dur(26, 0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1042 "2 years 2 months 03:00:06"
1043 );
1044 assert_eq!(
1045 &mon_dur(26, 0, 3 * 60 * 60 * 1_000_000),
1046 "2 years 2 months 03:00:00"
1047 );
1048 assert_eq!(
1049 &mon_dur(26, 0, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1050 "2 years 2 months 00:45:06"
1051 );
1052 assert_eq!(
1053 &mon_dur(26, 0, 45 * 60 * 1_000_000),
1054 "2 years 2 months 00:45:00"
1055 );
1056 assert_eq!(&mon_dur(26, 0, 6 * 1_000_000), "2 years 2 months 00:00:06");
1057
1058 assert_eq!(
1059 &mon_dur(26, -2, -6 * 1_000_000),
1060 "2 years 2 months -2 days -00:00:06"
1061 );
1062 assert_eq!(
1063 &mon_dur(26, -2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1064 "2 years 2 months -2 days -00:45:06"
1065 );
1066 assert_eq!(
1067 &mon_dur(26, -2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1068 "2 years 2 months -2 days -03:00:06"
1069 );
1070 assert_eq!(
1071 &mon_dur(
1072 26,
1073 0,
1074 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1075 ),
1076 "2 years 2 months -03:45:06"
1077 );
1078 assert_eq!(
1079 &mon_dur(26, 0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1080 "2 years 2 months -03:00:06"
1081 );
1082 assert_eq!(
1083 &mon_dur(26, 0, -3 * 60 * 60 * 1_000_000),
1084 "2 years 2 months -03:00:00"
1085 );
1086 assert_eq!(
1087 &mon_dur(26, 0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1088 "2 years 2 months -00:45:06"
1089 );
1090 assert_eq!(
1091 &mon_dur(26, 0, -45 * 60 * 1_000_000),
1092 "2 years 2 months -00:45:00"
1093 );
1094 assert_eq!(
1095 &mon_dur(26, 0, -6 * 1_000_000),
1096 "2 years 2 months -00:00:06"
1097 );
1098
1099 assert_eq!(&mon_dur(-1, 2, 6 * 1_000_000), "-1 months +2 days 00:00:06");
1100 assert_eq!(
1101 &mon_dur(-1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1102 "-1 months +2 days 00:45:06"
1103 );
1104 assert_eq!(
1105 &mon_dur(-1, 2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1106 "-1 months +2 days 03:00:06"
1107 );
1108 assert_eq!(
1109 &mon_dur(
1110 -26,
1111 0,
1112 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1113 ),
1114 "-2 years -2 months +03:45:06"
1115 );
1116 assert_eq!(
1117 &mon_dur(-26, 0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1118 "-2 years -2 months +03:00:06"
1119 );
1120 assert_eq!(
1121 &mon_dur(-26, 0, 3 * 60 * 60 * 1_000_000),
1122 "-2 years -2 months +03:00:00"
1123 );
1124 assert_eq!(
1125 &mon_dur(-26, 0, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1126 "-2 years -2 months +00:45:06"
1127 );
1128 assert_eq!(
1129 &mon_dur(-26, 0, 45 * 60 * 1_000_000),
1130 "-2 years -2 months +00:45:00"
1131 );
1132 assert_eq!(
1133 &mon_dur(-26, 0, 6 * 1_000_000),
1134 "-2 years -2 months +00:00:06"
1135 );
1136
1137 assert_eq!(
1138 &mon_dur(-26, -2, -6 * 1_000_000),
1139 "-2 years -2 months -2 days -00:00:06"
1140 );
1141 assert_eq!(
1142 &mon_dur(-26, -2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1143 "-2 years -2 months -2 days -00:45:06"
1144 );
1145 assert_eq!(
1146 &mon_dur(-26, -2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1147 "-2 years -2 months -2 days -03:00:06"
1148 );
1149 assert_eq!(
1150 &mon_dur(
1151 -26,
1152 0,
1153 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1154 ),
1155 "-2 years -2 months -03:45:06"
1156 );
1157 assert_eq!(
1158 &mon_dur(-26, 0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1159 "-2 years -2 months -03:00:06"
1160 );
1161 assert_eq!(
1162 &mon_dur(-26, 0, -3 * 60 * 60 * 1_000_000),
1163 "-2 years -2 months -03:00:00"
1164 );
1165 assert_eq!(
1166 &mon_dur(-26, 0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1167 "-2 years -2 months -00:45:06"
1168 );
1169 assert_eq!(
1170 &mon_dur(-26, 0, -45 * 60 * 1_000_000),
1171 "-2 years -2 months -00:45:00"
1172 );
1173 assert_eq!(
1174 &mon_dur(-26, 0, -6 * 1_000_000),
1175 "-2 years -2 months -00:00:06"
1176 );
1177 }
1178
1179 #[mz_ore::test]
1180 fn test_interval_value_truncate_low_fields() {
1181 use DateTimeField::*;
1182
1183 let mut test_cases = [
1184 (
1185 Year,
1186 None,
1187 (
1188 321,
1189 7,
1190 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1191 ),
1192 (26 * 12, 0, 0),
1193 ),
1194 (
1195 Month,
1196 None,
1197 (
1198 321,
1199 7,
1200 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1201 ),
1202 (321, 0, 0),
1203 ),
1204 (
1205 Day,
1206 None,
1207 (
1208 321,
1209 7,
1210 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1211 ),
1212 (321, 7, 0),
1213 ),
1214 (
1215 Hour,
1216 None,
1217 (
1218 321,
1219 7,
1220 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1221 ),
1222 (321, 7, 13 * 60 * 60 * 1_000_000),
1223 ),
1224 (
1225 Minute,
1226 None,
1227 (
1228 321,
1229 7,
1230 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1231 ),
1232 (321, 7, (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000)),
1233 ),
1234 (
1235 Second,
1236 None,
1237 (
1238 321,
1239 7,
1240 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1241 ),
1242 (
1243 321,
1244 7,
1245 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1246 ),
1247 ),
1248 (
1249 Second,
1250 Some(1),
1251 (
1252 321,
1253 7,
1254 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1255 ),
1256 (
1257 321,
1258 7,
1259 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 300_000,
1260 ),
1261 ),
1262 (
1263 Second,
1264 Some(0),
1265 (
1266 321,
1267 7,
1268 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1269 ),
1270 (
1271 321,
1272 7,
1273 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1274 ),
1275 ),
1276 ];
1277
1278 for test in test_cases.iter_mut() {
1279 let mut i = Interval::new((test.2).0, (test.2).1, (test.2).2);
1280 let j = Interval::new((test.3).0, (test.3).1, (test.3).2);
1281
1282 i.truncate_low_fields(test.0, test.1, RoundBehavior::Nearest)
1283 .unwrap();
1284
1285 if i != j {
1286 panic!(
1287 "test_interval_value_truncate_low_fields failed on {} \n actual: {:?} \n expected: {:?}",
1288 test.0, i, j
1289 );
1290 }
1291 }
1292 }
1293
1294 #[mz_ore::test]
1295 fn test_interval_value_truncate_high_fields() {
1296 use DateTimeField::*;
1297
1298 let mut test_cases = [
1300 (
1301 Year,
1302 (
1303 321,
1304 7,
1305 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1306 ),
1307 (
1308 321,
1309 7,
1310 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1311 ),
1312 ),
1313 (
1314 Month,
1315 (
1316 321,
1317 7,
1318 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1319 ),
1320 (
1321 9,
1322 7,
1323 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1324 ),
1325 ),
1326 (
1327 Day,
1328 (
1329 321,
1330 7,
1331 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1332 ),
1333 (
1334 0,
1335 7,
1336 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1337 ),
1338 ),
1339 (
1340 Hour,
1341 (
1342 321,
1343 7,
1344 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1345 ),
1346 (
1347 0,
1348 0,
1349 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1350 ),
1351 ),
1352 (
1353 Minute,
1354 (
1355 321,
1356 7,
1357 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1358 ),
1359 (0, 0, (45 * 60 * 1_000_000) + (21 * 1_000_000)),
1360 ),
1361 (
1362 Second,
1363 (
1364 321,
1365 7,
1366 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1367 ),
1368 (0, 0, 21 * 1_000_000),
1369 ),
1370 ];
1371
1372 for test in test_cases.iter_mut() {
1373 let mut i = Interval::new((test.1).0, (test.1).1, (test.1).2);
1374 let j = Interval::new((test.2).0, (test.2).1, (test.2).2);
1375
1376 i.truncate_high_fields(test.0);
1377
1378 if i != j {
1379 panic!(
1380 "test_interval_value_truncate_high_fields failed on {} \n actual: {:?} \n expected: {:?}",
1381 test.0, i, j
1382 );
1383 }
1384 }
1385 }
1386
1387 #[mz_ore::test]
1388 fn test_convert_date_time_unit() {
1389 assert_eq!(
1390 Some(1_123_200_000_000),
1391 Interval::convert_date_time_unit(
1392 DateTimeField::Day,
1393 DateTimeField::Microseconds,
1394 13i64
1395 )
1396 );
1397
1398 assert_eq!(
1399 Some(3_558_399_705),
1400 Interval::convert_date_time_unit(
1401 DateTimeField::Milliseconds,
1402 DateTimeField::Month,
1403 i64::MAX
1404 )
1405 );
1406
1407 assert_eq!(
1408 None,
1409 Interval::convert_date_time_unit(
1410 DateTimeField::Minute,
1411 DateTimeField::Second,
1412 i32::MAX
1413 )
1414 );
1415
1416 assert_eq!(
1417 Some(1),
1418 Interval::convert_date_time_unit(DateTimeField::Day, DateTimeField::Year, 365)
1419 );
1420
1421 assert_eq!(
1423 Some(360),
1424 Interval::convert_date_time_unit(DateTimeField::Year, DateTimeField::Day, 1)
1425 );
1426 }
1427
1428 #[mz_ore::test]
1429 fn proptest_packed_interval_roundtrips() {
1430 fn roundtrip_interval(og: Interval) {
1431 let packed = PackedInterval::from_value(og);
1432 let rnd = packed.into_value();
1433
1434 assert_eq!(og, rnd);
1435 }
1436
1437 proptest!(|(interval in any::<Interval>())| {
1438 roundtrip_interval(interval);
1439 });
1440 }
1441
1442 #[mz_ore::test]
1443 fn proptest_packed_interval_sorts() {
1444 fn sort_intervals(mut og: Vec<Interval>) {
1445 let mut packed: Vec<_> = og.iter().copied().map(PackedInterval::from_value).collect();
1446
1447 og.sort();
1448 packed.sort();
1449
1450 let rnd: Vec<_> = packed.into_iter().map(PackedInterval::into_value).collect();
1451
1452 assert_eq!(og, rnd);
1453 }
1454
1455 proptest!(|(interval in any::<Vec<Interval>>())| {
1456 sort_intervals(interval);
1457 });
1458 }
1459}