Skip to main content

riffdb_types/
time.rs

1//! Platform-independent date, timestamp, and logical-time values.
2
3use std::error::Error;
4use std::fmt;
5
6/// The number of nanoseconds in one second.
7pub const NANOS_PER_SECOND: u32 = 1_000_000_000;
8
9/// A UTC instant represented without a timezone or operating-system clock.
10#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
11pub struct Timestamp {
12    seconds: i64,
13    nanoseconds: u32,
14}
15
16impl Timestamp {
17    /// Validates and creates a Unix timestamp.
18    pub const fn new(seconds: i64, nanoseconds: u32) -> Result<Self, TimestampError> {
19        if nanoseconds >= NANOS_PER_SECOND {
20            return Err(TimestampError::NanosecondsOutOfRange { nanoseconds });
21        }
22        Ok(Self {
23            seconds,
24            nanoseconds,
25        })
26    }
27
28    /// Returns whole signed seconds since the Unix epoch.
29    #[must_use]
30    pub const fn seconds(self) -> i64 {
31        self.seconds
32    }
33
34    /// Returns the fractional nanosecond component.
35    #[must_use]
36    pub const fn nanoseconds(self) -> u32 {
37        self.nanoseconds
38    }
39
40    /// Returns the fractional nanosecond component.
41    #[must_use]
42    pub const fn nanos(self) -> u32 {
43        self.nanoseconds
44    }
45}
46
47/// A safe timestamp construction failure.
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49pub enum TimestampError {
50    /// The fractional component is not in `0..1_000_000_000`.
51    NanosecondsOutOfRange {
52        /// The invalid nanosecond component.
53        nanoseconds: u32,
54    },
55}
56
57impl fmt::Display for TimestampError {
58    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59        match self {
60            Self::NanosecondsOutOfRange { nanoseconds } => write!(
61                formatter,
62                "timestamp nanoseconds {nanoseconds} are outside 0..1_000_000_000"
63            ),
64        }
65    }
66}
67
68impl Error for TimestampError {}
69
70/// A calendar date represented as signed days since the Unix epoch.
71#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
72pub struct Date(i32);
73
74impl Date {
75    /// Creates a date from its canonical day count.
76    #[must_use]
77    pub const fn new(days_since_unix_epoch: i32) -> Self {
78        Self(days_since_unix_epoch)
79    }
80
81    /// Creates a date from its canonical day count.
82    #[must_use]
83    pub const fn from_days_since_unix_epoch(days: i32) -> Self {
84        Self::new(days)
85    }
86
87    /// Returns signed days since the Unix epoch.
88    #[must_use]
89    pub const fn days_since_unix_epoch(self) -> i32 {
90        self.0
91    }
92}
93
94/// Coordinator-supplied deterministic transaction time.
95///
96/// This wrapper intentionally has no wall-clock constructor. Admission code must
97/// supply a validated [`Timestamp`], and deterministic command code receives only
98/// the resulting logical value.
99#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
100pub struct LogicalTime(Timestamp);
101
102impl LogicalTime {
103    /// Wraps a coordinator-supplied timestamp as deterministic logical time.
104    #[must_use]
105    pub const fn new(timestamp: Timestamp) -> Self {
106        Self(timestamp)
107    }
108
109    /// Returns the underlying UTC timestamp.
110    #[must_use]
111    pub const fn timestamp(self) -> Timestamp {
112        self.0
113    }
114}
115
116// Exhaustive non-durable ownership inventories for exact-provider admission.
117crate::inspect_owned_capacity_copy_v1!(Timestamp);
118
119crate::inspect_owned_capacity_copy_v1!(Date);
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn timestamp_accepts_only_canonical_nanoseconds() {
127        assert!(Timestamp::new(i64::MIN, 0).is_ok());
128        assert!(Timestamp::new(i64::MAX, NANOS_PER_SECOND - 1).is_ok());
129        assert_eq!(
130            Timestamp::new(0, NANOS_PER_SECOND),
131            Err(TimestampError::NanosecondsOutOfRange {
132                nanoseconds: NANOS_PER_SECOND
133            })
134        );
135    }
136
137    #[test]
138    fn date_and_logical_time_are_exact_wrappers() {
139        let date = Date::from_days_since_unix_epoch(-1);
140        assert_eq!(date.days_since_unix_epoch(), -1);
141
142        let timestamp = Timestamp::new(42, 7).expect("valid timestamp");
143        let logical = LogicalTime::new(timestamp);
144        assert_eq!(logical.timestamp(), timestamp);
145    }
146}