1use serde::{Deserialize, Serialize};
47use std::fmt;
48use std::time::{SystemTime, UNIX_EPOCH};
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
56pub struct ValidTimeRange {
57 pub start: Option<i64>,
59 pub end: Option<i64>,
61}
62
63impl ValidTimeRange {
64 #[must_use]
66 pub const fn unbounded() -> Self {
67 Self {
68 start: None,
69 end: None,
70 }
71 }
72
73 #[must_use]
75 pub const fn from(start: i64) -> Self {
76 Self {
77 start: Some(start),
78 end: None,
79 }
80 }
81
82 #[must_use]
84 pub const fn until(end: i64) -> Self {
85 Self {
86 start: None,
87 end: Some(end),
88 }
89 }
90
91 #[must_use]
93 pub const fn between(start: i64, end: i64) -> Self {
94 Self {
95 start: Some(start),
96 end: Some(end),
97 }
98 }
99
100 #[must_use]
102 pub fn from_now() -> Self {
103 Self::from(current_timestamp())
104 }
105
106 #[must_use]
108 pub fn until_now() -> Self {
109 Self::until(current_timestamp())
110 }
111
112 #[must_use]
116 pub const fn contains(&self, timestamp: i64) -> bool {
117 let after_start = match self.start {
118 Some(s) => timestamp >= s,
119 None => true,
120 };
121 let before_end = match self.end {
122 Some(e) => timestamp < e,
123 None => true,
124 };
125 after_start && before_end
126 }
127
128 #[must_use]
130 pub fn is_current(&self) -> bool {
131 self.contains(current_timestamp())
132 }
133
134 #[must_use]
136 pub const fn is_unbounded(&self) -> bool {
137 self.start.is_none() && self.end.is_none()
138 }
139
140 #[must_use]
142 pub fn has_started(&self) -> bool {
143 self.start.is_none_or(|s| current_timestamp() >= s)
144 }
145
146 #[must_use]
148 pub fn has_ended(&self) -> bool {
149 self.end.is_some_and(|e| current_timestamp() >= e)
150 }
151
152 #[must_use]
154 pub fn overlap(&self, other: &Self) -> Option<Self> {
155 let start = match (self.start, other.start) {
156 (Some(a), Some(b)) => Some(a.max(b)),
157 (Some(a), None) => Some(a),
158 (None, Some(b)) => Some(b),
159 (None, None) => None,
160 };
161
162 let end = match (self.end, other.end) {
163 (Some(a), Some(b)) => Some(a.min(b)),
164 (Some(a), None) => Some(a),
165 (None, Some(b)) => Some(b),
166 (None, None) => None,
167 };
168
169 if let (Some(s), Some(e)) = (start, end)
171 && s >= e
172 {
173 return None;
174 }
175
176 Some(Self { start, end })
177 }
178
179 #[must_use]
183 pub const fn close_at(self, end: i64) -> Self {
184 Self {
185 start: self.start,
186 end: Some(end),
187 }
188 }
189
190 #[must_use]
192 pub fn close_now(self) -> Self {
193 self.close_at(current_timestamp())
194 }
195}
196
197impl Default for ValidTimeRange {
198 fn default() -> Self {
199 Self::unbounded()
200 }
201}
202
203impl fmt::Display for ValidTimeRange {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 match (self.start, self.end) {
206 (None, None) => write!(f, "[∞, ∞)"),
207 (Some(s), None) => write!(f, "[{s}, ∞)"),
208 (None, Some(e)) => write!(f, "[∞, {e})"),
209 (Some(s), Some(e)) => write!(f, "[{s}, {e})"),
210 }
211 }
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
220pub struct TransactionTime {
221 timestamp: i64,
223}
224
225impl TransactionTime {
226 #[must_use]
228 pub fn now() -> Self {
229 Self {
230 timestamp: current_timestamp(),
231 }
232 }
233
234 #[must_use]
238 pub const fn at(timestamp: i64) -> Self {
239 Self { timestamp }
240 }
241
242 #[must_use]
244 pub const fn timestamp(&self) -> i64 {
245 self.timestamp
246 }
247
248 #[must_use]
250 pub const fn is_before(&self, other: &Self) -> bool {
251 self.timestamp < other.timestamp
252 }
253
254 #[must_use]
256 pub const fn is_after(&self, other: &Self) -> bool {
257 self.timestamp > other.timestamp
258 }
259
260 #[must_use]
262 pub const fn was_known_at(&self, timestamp: i64) -> bool {
263 self.timestamp <= timestamp
264 }
265}
266
267impl Default for TransactionTime {
268 fn default() -> Self {
269 Self::now()
270 }
271}
272
273impl fmt::Display for TransactionTime {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 write!(f, "tx@{}", self.timestamp)
276 }
277}
278
279impl From<i64> for TransactionTime {
280 fn from(timestamp: i64) -> Self {
281 Self::at(timestamp)
282 }
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
290pub struct BitemporalPoint {
291 pub valid_at: i64,
293 pub as_of: i64,
295}
296
297impl BitemporalPoint {
298 #[must_use]
300 pub const fn new(valid_at: i64, as_of: i64) -> Self {
301 Self { valid_at, as_of }
302 }
303
304 #[must_use]
306 pub fn now() -> Self {
307 let ts = current_timestamp();
308 Self {
309 valid_at: ts,
310 as_of: ts,
311 }
312 }
313
314 #[must_use]
316 pub const fn is_visible(&self, valid_time: &ValidTimeRange, tx_time: &TransactionTime) -> bool {
317 valid_time.contains(self.valid_at) && tx_time.was_known_at(self.as_of)
318 }
319}
320
321impl Default for BitemporalPoint {
322 fn default() -> Self {
323 Self::now()
324 }
325}
326
327impl fmt::Display for BitemporalPoint {
328 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329 write!(f, "valid@{}, as_of@{}", self.valid_at, self.as_of)
330 }
331}
332
333#[must_use]
335#[allow(clippy::cast_possible_wrap)]
336pub fn current_timestamp() -> i64 {
337 SystemTime::now()
339 .duration_since(UNIX_EPOCH)
340 .map(|d| d.as_secs() as i64)
341 .unwrap_or(0)
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn test_valid_time_range_unbounded() {
350 let range = ValidTimeRange::unbounded();
351 assert!(range.is_unbounded());
352 assert!(range.contains(0));
353 assert!(range.contains(i64::MAX));
354 assert!(range.contains(i64::MIN));
355 }
356
357 #[test]
358 fn test_valid_time_range_from() {
359 let range = ValidTimeRange::from(100);
360 assert!(!range.is_unbounded());
361 assert!(!range.contains(99));
362 assert!(range.contains(100));
363 assert!(range.contains(101));
364 assert!(range.contains(i64::MAX));
365 }
366
367 #[test]
368 fn test_valid_time_range_until() {
369 let range = ValidTimeRange::until(100);
370 assert!(!range.is_unbounded());
371 assert!(range.contains(99));
372 assert!(!range.contains(100)); assert!(range.contains(i64::MIN));
374 }
375
376 #[test]
377 fn test_valid_time_range_between() {
378 let range = ValidTimeRange::between(100, 200);
379 assert!(!range.is_unbounded());
380 assert!(!range.contains(99));
381 assert!(range.contains(100));
382 assert!(range.contains(150));
383 assert!(range.contains(199));
384 assert!(!range.contains(200)); }
386
387 #[test]
388 fn test_valid_time_range_overlap() {
389 let r1 = ValidTimeRange::between(100, 200);
390 let r2 = ValidTimeRange::between(150, 250);
391
392 let overlap = r1.overlap(&r2);
393 assert!(overlap.is_some());
394 let overlap = overlap.unwrap();
395 assert_eq!(overlap.start, Some(150));
396 assert_eq!(overlap.end, Some(200));
397 }
398
399 #[test]
400 fn test_valid_time_range_no_overlap() {
401 let r1 = ValidTimeRange::between(100, 200);
402 let r2 = ValidTimeRange::between(200, 300);
403
404 let overlap = r1.overlap(&r2);
405 assert!(overlap.is_none());
406 }
407
408 #[test]
409 fn test_valid_time_range_close() {
410 let range = ValidTimeRange::from(100);
411 assert!(range.end.is_none());
412
413 let closed = range.close_at(200);
414 assert_eq!(closed.start, Some(100));
415 assert_eq!(closed.end, Some(200));
416 }
417
418 #[test]
419 fn test_transaction_time() {
420 let tx = TransactionTime::now();
421 assert!(tx.timestamp() > 0);
422
423 let tx2 = TransactionTime::at(100);
424 assert_eq!(tx2.timestamp(), 100);
425 assert!(tx2.is_before(&tx));
426 assert!(tx.is_after(&tx2));
427 }
428
429 #[test]
430 fn test_transaction_time_was_known_at() {
431 let tx = TransactionTime::at(100);
432 assert!(tx.was_known_at(100));
433 assert!(tx.was_known_at(101));
434 assert!(!tx.was_known_at(99));
435 }
436
437 #[test]
438 fn test_bitemporal_point() {
439 let point = BitemporalPoint::new(150, 200);
440
441 let valid_time = ValidTimeRange::between(100, 200);
443 let tx_time = TransactionTime::at(50);
444
445 assert!(point.is_visible(&valid_time, &tx_time));
449
450 let tx_time_future = TransactionTime::at(250);
452 assert!(!point.is_visible(&valid_time, &tx_time_future));
453
454 let valid_time_past = ValidTimeRange::between(50, 100);
456 assert!(!point.is_visible(&valid_time_past, &tx_time));
457 }
458
459 #[test]
460 fn test_display_formats() {
461 assert_eq!(ValidTimeRange::unbounded().to_string(), "[∞, ∞)");
462 assert_eq!(ValidTimeRange::from(100).to_string(), "[100, ∞)");
463 assert_eq!(ValidTimeRange::until(200).to_string(), "[∞, 200)");
464 assert_eq!(ValidTimeRange::between(100, 200).to_string(), "[100, 200)");
465
466 assert_eq!(TransactionTime::at(100).to_string(), "tx@100");
467 assert_eq!(
468 BitemporalPoint::new(100, 200).to_string(),
469 "valid@100, as_of@200"
470 );
471 }
472
473 #[test]
474 fn test_defaults() {
475 let valid_time = ValidTimeRange::default();
476 assert!(valid_time.is_unbounded());
477
478 let tx_time = TransactionTime::default();
479 assert!(tx_time.timestamp() > 0);
480
481 let point = BitemporalPoint::default();
482 assert!(point.valid_at > 0);
483 assert!(point.as_of > 0);
484 }
485}