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