1use std::fmt;
15
16#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
19pub struct Timestamp(pub i64);
20
21impl Default for Timestamp {
22 fn default() -> Self {
24 Self::unset()
25 }
26}
27
28impl Timestamp {
29 pub const fn unset() -> Self {
30 Timestamp(i64::MIN)
31 }
32 pub const fn unstarted() -> Self {
33 Timestamp(i64::MIN + 1)
34 }
35 pub const fn pre_stream() -> Self {
36 Timestamp(i64::MIN + 2)
37 }
38 pub const fn min() -> Self {
39 Timestamp(i64::MIN + 3)
40 }
41 pub const fn max() -> Self {
42 Timestamp(i64::MAX - 3)
43 }
44 pub const fn post_stream() -> Self {
45 Timestamp(i64::MAX - 2)
46 }
47 pub const fn one_over_post_stream() -> Self {
48 Timestamp(i64::MAX - 1)
49 }
50 pub const fn done() -> Self {
51 Timestamp(i64::MAX)
52 }
53
54 pub fn is_range_value(self) -> bool {
56 self >= Self::min() && self <= Self::max()
57 }
58
59 pub fn is_allowed_in_stream(self) -> bool {
62 self >= Self::pre_stream() && self <= Self::post_stream()
63 }
64
65 pub fn next_allowed_in_stream(self) -> Self {
69 if self >= Self::max() || self == Self::pre_stream() {
70 Self::one_over_post_stream()
71 } else if self < Self::min() {
72 Self::min()
73 } else {
74 Timestamp(self.0 + 1)
75 }
76 }
77
78 pub fn has_next_allowed_in_stream(self) -> bool {
79 self < Self::max() && self != Self::pre_stream()
80 }
81
82 pub fn previous_allowed_in_stream(self) -> Self {
83 if self <= Self::min() || self == Self::post_stream() {
84 Self::unstarted()
85 } else if self > Self::max() {
86 Self::max()
87 } else {
88 Timestamp(self.0 - 1)
89 }
90 }
91}
92
93impl std::ops::Add<i64> for Timestamp {
96 type Output = Timestamp;
97 fn add(self, rhs: i64) -> Timestamp {
98 Timestamp(self.0.saturating_add(rhs))
99 }
100}
101
102impl std::ops::Sub<i64> for Timestamp {
103 type Output = Timestamp;
104 fn sub(self, rhs: i64) -> Timestamp {
105 Timestamp(self.0.saturating_sub(rhs))
106 }
107}
108
109impl fmt::Display for Timestamp {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 let name = match *self {
112 t if t == Self::unset() => "Unset",
113 t if t == Self::unstarted() => "Unstarted",
114 t if t == Self::pre_stream() => "PreStream",
115 t if t == Self::post_stream() => "PostStream",
116 t if t == Self::one_over_post_stream() => "OneOverPostStream",
117 t if t == Self::done() => "Done",
118 _ => return write!(f, "{}", self.0),
119 };
120 f.write_str(name)
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn sentinels_are_ordered() {
130 assert!(Timestamp::unset() < Timestamp::unstarted());
131 assert!(Timestamp::unstarted() < Timestamp::pre_stream());
132 assert!(Timestamp::pre_stream() < Timestamp::min());
133 assert!(Timestamp::min() < Timestamp::max());
134 assert!(Timestamp::max() < Timestamp::post_stream());
135 assert!(Timestamp::post_stream() < Timestamp::one_over_post_stream());
136 assert!(Timestamp::one_over_post_stream() < Timestamp::done());
137 }
138
139 #[test]
140 fn default_is_unset() {
141 assert_eq!(Timestamp::default(), Timestamp::unset());
142 }
143
144 #[test]
145 fn allowed_in_stream_range() {
146 assert!(Timestamp::pre_stream().is_allowed_in_stream());
147 assert!(Timestamp(0).is_allowed_in_stream());
148 assert!(Timestamp::post_stream().is_allowed_in_stream());
149 assert!(!Timestamp::unset().is_allowed_in_stream());
150 assert!(!Timestamp::unstarted().is_allowed_in_stream());
151 assert!(!Timestamp::one_over_post_stream().is_allowed_in_stream());
152 assert!(!Timestamp::done().is_allowed_in_stream());
153 }
154
155 #[test]
156 fn range_value_only_covers_data_interval() {
157 assert!(Timestamp(0).is_range_value());
158 assert!(Timestamp::min().is_range_value());
159 assert!(Timestamp::max().is_range_value());
160 assert!(!Timestamp::pre_stream().is_range_value());
161 assert!(!Timestamp::post_stream().is_range_value());
162 }
163
164 #[test]
165 fn next_allowed_in_stream_cases() {
166 assert_eq!(Timestamp(5).next_allowed_in_stream(), Timestamp(6));
167 assert_eq!(
169 Timestamp::pre_stream().next_allowed_in_stream(),
170 Timestamp::one_over_post_stream()
171 );
172 assert_eq!(
173 Timestamp::max().next_allowed_in_stream(),
174 Timestamp::one_over_post_stream()
175 );
176 assert_eq!(
177 Timestamp::post_stream().next_allowed_in_stream(),
178 Timestamp::one_over_post_stream()
179 );
180 assert_eq!(
182 Timestamp::unstarted().next_allowed_in_stream(),
183 Timestamp::min()
184 );
185 assert_eq!(
186 Timestamp::unset().next_allowed_in_stream(),
187 Timestamp::min()
188 );
189 }
190
191 #[test]
192 fn previous_allowed_in_stream_cases() {
193 assert_eq!(Timestamp(5).previous_allowed_in_stream(), Timestamp(4));
194 assert_eq!(
195 Timestamp::min().previous_allowed_in_stream(),
196 Timestamp::unstarted()
197 );
198 assert_eq!(
199 Timestamp::post_stream().previous_allowed_in_stream(),
200 Timestamp::unstarted()
201 );
202 assert_eq!(
203 Timestamp::done().previous_allowed_in_stream(),
204 Timestamp::max()
205 );
206 assert_eq!(
207 Timestamp::one_over_post_stream().previous_allowed_in_stream(),
208 Timestamp::max()
209 );
210 }
211
212 #[test]
213 fn has_next_allowed_in_stream() {
214 assert!(Timestamp(0).has_next_allowed_in_stream());
215 assert!(!Timestamp::pre_stream().has_next_allowed_in_stream());
216 assert!(!Timestamp::max().has_next_allowed_in_stream());
217 assert!(!Timestamp::done().has_next_allowed_in_stream());
218 }
219
220 #[test]
221 fn arithmetic_saturates_instead_of_overflowing() {
222 assert_eq!(Timestamp::done() + 1, Timestamp::done());
223 assert_eq!(Timestamp::unset() - 1, Timestamp::unset());
224 assert_eq!(Timestamp(10) + 5, Timestamp(15));
225 assert_eq!(Timestamp(10) - 5, Timestamp(5));
226 }
227
228 #[test]
229 fn display_names_sentinels() {
230 assert_eq!(Timestamp::done().to_string(), "Done");
231 assert_eq!(Timestamp::pre_stream().to_string(), "PreStream");
232 assert_eq!(Timestamp(7).to_string(), "7");
233 }
234}