1use std::{
5 fmt::Debug,
6 ops::{Add, Rem, Sub},
7};
8
9use reifydb_value::value::{date::Date, datetime::DateTime, duration::Duration, time::Time};
10use serde::{Deserialize, Serialize};
11
12pub trait Slot:
13 Copy
14 + Ord
15 + Debug
16 + Add<Self::Duration, Output = Self>
17 + Sub<Self, Output = Self::Duration>
18 + Rem<Self::Duration, Output = Self::Duration>
19 + Sub<Self::Duration, Output = Self>
20{
21 type Duration: Copy + Ord + Debug + IsZero;
22
23 fn order_key(&self) -> u64;
24
25 fn from_order_key(order_key: u64) -> Self;
26}
27
28pub trait IsZero {
29 fn is_zero(&self) -> bool;
30}
31
32impl IsZero for u64 {
33 #[inline]
34 fn is_zero(&self) -> bool {
35 *self == 0
36 }
37}
38
39impl IsZero for Duration {
40 #[inline]
41 fn is_zero(&self) -> bool {
42 *self == Duration::zero()
43 }
44}
45
46impl IsZero for DateTime {
47 #[inline]
48 fn is_zero(&self) -> bool {
49 *self == DateTime::default()
50 }
51}
52
53impl IsZero for Date {
54 #[inline]
55 fn is_zero(&self) -> bool {
56 *self == Date::default()
57 }
58}
59
60impl IsZero for Time {
61 #[inline]
62 fn is_zero(&self) -> bool {
63 *self == Time::default()
64 }
65}
66
67impl Slot for u64 {
68 type Duration = u64;
69
70 fn order_key(&self) -> u64 {
71 *self
72 }
73
74 fn from_order_key(order_key: u64) -> Self {
75 order_key
76 }
77}
78
79impl Slot for DateTime {
80 type Duration = Duration;
81
82 fn order_key(&self) -> u64 {
83 self.to_nanos()
84 }
85
86 fn from_order_key(order_key: u64) -> Self {
87 DateTime::from_nanos(order_key)
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
92pub struct WindowSpan<T> {
93 pub start: T,
94 pub end: T,
95}
96
97impl<T> WindowSpan<T>
98where
99 T: Slot,
100{
101 #[inline]
102 pub fn for_slot(slot: T, duration: T::Duration) -> Self {
103 assert!(!duration.is_zero(), "WindowSpan::for_slot: duration must be > 0");
104 let start = slot - (slot % duration);
105 Self {
106 start,
107 end: start + duration,
108 }
109 }
110
111 #[inline]
112 pub fn new(start: T, end: T) -> Self {
113 assert!(start < end, "WindowSpan::new: start ({start:?}) must be < end ({end:?})");
114 Self {
115 start,
116 end,
117 }
118 }
119
120 #[inline]
121 pub fn duration(&self) -> T::Duration {
122 self.end - self.start
123 }
124
125 #[inline]
126 pub fn contains(&self, slot: T) -> bool {
127 slot >= self.start && slot < self.end
128 }
129
130 #[inline]
131 pub fn next(&self) -> Self {
132 let d = self.duration();
133 Self {
134 start: self.end,
135 end: self.end + d,
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn for_slot_aligns_to_duration() {
146 assert_eq!(WindowSpan::<u64>::for_slot(123, 60), WindowSpan::new(120u64, 180));
147 assert_eq!(WindowSpan::<u64>::for_slot(0, 60), WindowSpan::new(0u64, 60));
148 assert_eq!(WindowSpan::<u64>::for_slot(60, 60), WindowSpan::new(60u64, 120));
149 }
150
151 #[test]
152 fn for_slot_aligns_datetime_to_duration() {
153 let coord = DateTime::from_ymd_hms(2024, 1, 15, 10, 30, 25).unwrap();
154 let one_second = Duration::from_seconds(1).unwrap();
155 let one_minute = Duration::from_seconds(60).unwrap();
156
157 let sec = WindowSpan::for_slot(coord, one_second);
159 assert_eq!(sec.start, DateTime::from_ymd_hms(2024, 1, 15, 10, 30, 25).unwrap());
160 assert_eq!(sec.end, DateTime::from_ymd_hms(2024, 1, 15, 10, 30, 26).unwrap());
161 assert_eq!(sec.duration(), one_second);
162
163 let min = WindowSpan::for_slot(coord, one_minute);
165 assert_eq!(min.start, DateTime::from_ymd_hms(2024, 1, 15, 10, 30, 0).unwrap());
166 assert_eq!(min.end, DateTime::from_ymd_hms(2024, 1, 15, 10, 31, 0).unwrap());
167 assert!(min.contains(coord));
168 assert!(!min.contains(min.end));
169 }
170
171 #[test]
172 fn contains_is_half_open() {
173 let span = WindowSpan::new(100u64, 200);
174 assert!(span.contains(100));
175 assert!(span.contains(199));
176 assert!(!span.contains(200));
177 assert!(!span.contains(99));
178 }
179
180 #[test]
181 fn boundary_slot_belongs_to_next_window() {
182 let cur = WindowSpan::<u64>::for_slot(60, 60);
185 let nxt = cur.next();
186 assert!(!cur.contains(120));
187 assert!(nxt.contains(120));
188 assert_eq!(nxt, WindowSpan::new(120u64, 180));
189 }
190
191 #[test]
192 #[should_panic(expected = "duration must be > 0")]
193 fn zero_duration_panics() {
194 WindowSpan::<u64>::for_slot(10, 0);
195 }
196
197 #[test]
198 #[should_panic(expected = "must be <")]
199 fn empty_span_panics() {
200 WindowSpan::new(100u64, 100);
201 }
202
203 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
206 struct Tick(u64);
207
208 impl Add<u64> for Tick {
209 type Output = Tick;
210 fn add(self, rhs: u64) -> Tick {
211 Tick(self.0 + rhs)
212 }
213 }
214 impl Sub<Tick> for Tick {
215 type Output = u64;
216 fn sub(self, rhs: Tick) -> u64 {
217 self.0 - rhs.0
218 }
219 }
220 impl Sub<u64> for Tick {
221 type Output = Tick;
222 fn sub(self, rhs: u64) -> Tick {
223 Tick(self.0 - rhs)
224 }
225 }
226 impl Rem<u64> for Tick {
227 type Output = u64;
228 fn rem(self, rhs: u64) -> u64 {
229 self.0 % rhs
230 }
231 }
232 impl Slot for Tick {
233 type Duration = u64;
234
235 fn order_key(&self) -> u64 {
236 self.0
237 }
238
239 fn from_order_key(order_key: u64) -> Self {
240 Tick(order_key)
241 }
242 }
243
244 #[test]
245 fn newtype_coord_works() {
246 let span = WindowSpan::<Tick>::for_slot(Tick(125), 10);
247 assert_eq!(span, WindowSpan::new(Tick(120), Tick(130)));
248 assert!(span.contains(Tick(120)));
249 assert!(!span.contains(Tick(130)));
250 }
251}