proto_types/common/
interval.rs1use std::cmp::Ordering;
2
3use thiserror::Error;
4
5use crate::{common::Interval, constants::NANOS_PER_SECOND, Duration, Timestamp};
6
7#[derive(Debug, Error, PartialEq, Eq, Clone)]
9pub enum IntervalError {
10 #[error("Start and end time must be both defined or undefined")]
11 InvalidPairing,
12 #[error("Interval contains an invalid Timestamp")]
13 InvalidTimestamp,
14 #[error("Interval's end_time is before its start_time")]
15 EndTimeBeforeStartTime,
16 #[error("Interval arithmetic resulted in a value outside its representable range")]
17 OutOfRange,
18 #[error("Interval conversion error: {0}")]
19 ConversionError(String),
20}
21
22fn validate_interval(
23 start: Option<Timestamp>,
24 end: Option<Timestamp>,
25) -> Result<(), IntervalError> {
26 if end < start {
27 Err(IntervalError::EndTimeBeforeStartTime)
28 } else if !((start.is_some() && end.is_some()) || start.is_none() && end.is_none()) {
29 Err(IntervalError::InvalidPairing)
30 } else {
31 Ok(())
32 }
33}
34
35impl Interval {
36 pub fn new(
39 start_time: Option<Timestamp>,
40 end_time: Option<Timestamp>,
41 ) -> Result<Self, IntervalError> {
42 validate_interval(start_time, end_time)?;
43
44 Ok(Interval {
45 start_time,
46 end_time,
47 })
48 }
49
50 pub fn is_valid(&self) -> bool {
52 validate_interval(self.start_time, self.end_time).is_ok()
53 }
54
55 pub fn is_empty(&self) -> bool {
57 self
58 .start_time
59 .as_ref()
60 .zip(self.end_time.as_ref())
61 .map_or_else(|| false, |(start, end)| start == end)
62 }
63
64 pub fn is_unspecified(&self) -> bool {
66 self.start_time.is_none() && self.end_time.is_none()
67 }
68}
69
70impl TryFrom<Interval> for Duration {
71 type Error = IntervalError;
72 fn try_from(value: Interval) -> Result<Self, Self::Error> {
73 let result = value.start_time.zip(value.end_time).map(|(start, end)| {
74 let mut seconds_diff = end.seconds - start.seconds;
75 let mut nanos_diff = end.nanos - start.nanos;
76
77 if nanos_diff < 0 {
78 seconds_diff -= 1;
79 nanos_diff += NANOS_PER_SECOND;
80 } else if nanos_diff >= NANOS_PER_SECOND {
81 seconds_diff += 1;
82 nanos_diff -= NANOS_PER_SECOND;
83 }
84
85 Duration {
86 seconds: seconds_diff,
87 nanos: nanos_diff,
88 }
89 });
90
91 result.ok_or(IntervalError::ConversionError(
92 "Cannot convert to Duration due to missing start or end time".to_string(),
93 ))
94 }
95}
96
97impl PartialOrd for Interval {
98 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
99 if !self.is_valid() || !other.is_valid() {
100 return None;
101 }
102
103 if self.is_unspecified() {
104 if other.is_unspecified() {
105 Some(Ordering::Equal)
106 } else {
107 Some(Ordering::Less)
108 }
109 } else if other.is_unspecified() {
110 Some(Ordering::Greater)
111 } else {
112 let self_as_duration: Result<Duration, IntervalError> = (*self).try_into();
113 let other_as_duration: Result<Duration, IntervalError> = (*other).try_into();
114
115 if self_as_duration.is_ok() && other_as_duration.is_err() {
116 Some(Ordering::Greater)
117 } else if self_as_duration.is_err() && other_as_duration.is_ok() {
118 Some(Ordering::Less)
119 } else if self_as_duration.is_err() && other_as_duration.is_err() {
120 Some(Ordering::Equal)
121 } else {
122 self_as_duration
123 .unwrap()
124 .partial_cmp(&other_as_duration.unwrap())
125 }
126 }
127 }
128}