1use std::error::Error;
4use std::fmt;
5
6pub const NANOS_PER_SECOND: u32 = 1_000_000_000;
8
9#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
11pub struct Timestamp {
12 seconds: i64,
13 nanoseconds: u32,
14}
15
16impl Timestamp {
17 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 #[must_use]
30 pub const fn seconds(self) -> i64 {
31 self.seconds
32 }
33
34 #[must_use]
36 pub const fn nanoseconds(self) -> u32 {
37 self.nanoseconds
38 }
39
40 #[must_use]
42 pub const fn nanos(self) -> u32 {
43 self.nanoseconds
44 }
45}
46
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49pub enum TimestampError {
50 NanosecondsOutOfRange {
52 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#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
72pub struct Date(i32);
73
74impl Date {
75 #[must_use]
77 pub const fn new(days_since_unix_epoch: i32) -> Self {
78 Self(days_since_unix_epoch)
79 }
80
81 #[must_use]
83 pub const fn from_days_since_unix_epoch(days: i32) -> Self {
84 Self::new(days)
85 }
86
87 #[must_use]
89 pub const fn days_since_unix_epoch(self) -> i32 {
90 self.0
91 }
92}
93
94#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
100pub struct LogicalTime(Timestamp);
101
102impl LogicalTime {
103 #[must_use]
105 pub const fn new(timestamp: Timestamp) -> Self {
106 Self(timestamp)
107 }
108
109 #[must_use]
111 pub const fn timestamp(self) -> Timestamp {
112 self.0
113 }
114}
115
116crate::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}