1use std::{
19 cmp::Ordering,
20 fmt::Display,
21 ops::{Add, AddAssign, Deref, Sub, SubAssign},
22 str::FromStr,
23};
24
25use chrono::{DateTime, Utc};
26use serde::{Deserialize, Serialize};
27
28#[repr(C)]
30#[derive(
31 Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
32)]
33pub struct UnixNanos(u64);
34
35impl UnixNanos {
36 #[must_use]
38 pub const fn as_u64(&self) -> u64 {
39 self.0
40 }
41
42 #[must_use]
44 pub const fn as_i64(&self) -> i64 {
45 self.0 as i64
46 }
47
48 #[must_use]
50 pub const fn as_f64(&self) -> f64 {
51 self.0 as f64
52 }
53}
54
55impl Deref for UnixNanos {
56 type Target = u64;
57
58 fn deref(&self) -> &Self::Target {
59 &self.0
60 }
61}
62
63impl PartialEq<u64> for UnixNanos {
64 fn eq(&self, other: &u64) -> bool {
65 self.0 == *other
66 }
67}
68
69impl PartialOrd<u64> for UnixNanos {
70 fn partial_cmp(&self, other: &u64) -> Option<Ordering> {
71 self.0.partial_cmp(other)
72 }
73}
74
75impl PartialEq<Option<u64>> for UnixNanos {
76 fn eq(&self, other: &Option<u64>) -> bool {
77 match other {
78 Some(value) => self.0 == *value,
79 None => false,
80 }
81 }
82}
83
84impl PartialOrd<Option<u64>> for UnixNanos {
85 fn partial_cmp(&self, other: &Option<u64>) -> Option<Ordering> {
86 match other {
87 Some(value) => self.0.partial_cmp(value),
88 None => Some(Ordering::Greater),
89 }
90 }
91}
92
93impl From<u64> for UnixNanos {
94 fn from(value: u64) -> Self {
95 Self(value)
96 }
97}
98
99impl From<UnixNanos> for u64 {
100 fn from(value: UnixNanos) -> Self {
101 value.0
102 }
103}
104
105impl From<&str> for UnixNanos {
106 fn from(value: &str) -> Self {
107 Self(
108 value
109 .parse()
110 .expect("`value` should be a valid integer string"),
111 )
112 }
113}
114
115impl From<String> for UnixNanos {
116 fn from(value: String) -> Self {
117 Self::from(value.as_str())
118 }
119}
120
121impl From<DateTime<Utc>> for UnixNanos {
122 fn from(value: DateTime<Utc>) -> Self {
123 Self::from(value.timestamp_nanos_opt().expect("Invalid timestamp") as u64)
124 }
125}
126
127impl FromStr for UnixNanos {
128 type Err = std::num::ParseIntError;
129
130 fn from_str(s: &str) -> Result<Self, Self::Err> {
131 s.parse().map(UnixNanos)
132 }
133}
134
135impl Add for UnixNanos {
136 type Output = Self;
137 fn add(self, rhs: Self) -> Self::Output {
138 Self(
139 self.0
140 .checked_add(rhs.0)
141 .expect("Error adding with overflow"),
142 )
143 }
144}
145
146impl Sub for UnixNanos {
147 type Output = Self;
148 fn sub(self, rhs: Self) -> Self::Output {
149 Self(
150 self.0
151 .checked_sub(rhs.0)
152 .expect("Error subtracting with underflow"),
153 )
154 }
155}
156
157impl Add<u64> for UnixNanos {
158 type Output = Self;
159
160 fn add(self, rhs: u64) -> Self::Output {
161 Self(self.0.checked_add(rhs).expect("Error adding with overflow"))
162 }
163}
164
165impl Sub<u64> for UnixNanos {
166 type Output = Self;
167
168 fn sub(self, rhs: u64) -> Self::Output {
169 Self(
170 self.0
171 .checked_sub(rhs)
172 .expect("Error subtracting with underflow"),
173 )
174 }
175}
176
177impl<T: Into<u64>> AddAssign<T> for UnixNanos {
178 fn add_assign(&mut self, other: T) {
179 let other_u64 = other.into();
180 self.0 = self
181 .0
182 .checked_add(other_u64)
183 .expect("Error adding with overflow");
184 }
185}
186
187impl<T: Into<u64>> SubAssign<T> for UnixNanos {
188 fn sub_assign(&mut self, other: T) {
189 let other_u64 = other.into();
190 self.0 = self
191 .0
192 .checked_sub(other_u64)
193 .expect("Error subtracting with underflow");
194 }
195}
196
197impl Display for UnixNanos {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 write!(f, "{}", self.0)
200 }
201}
202
203pub type DurationNanos = u64;
205
206#[cfg(test)]
210mod tests {
211 use rstest::rstest;
212
213 use super::*;
214
215 #[rstest]
216 fn test_new() {
217 let nanos = UnixNanos::from(123);
218 assert_eq!(nanos.as_u64(), 123);
219 assert_eq!(nanos.as_i64(), 123);
220 }
221
222 #[rstest]
223 fn test_default() {
224 let nanos = UnixNanos::default();
225 assert_eq!(nanos.as_u64(), 0);
226 assert_eq!(nanos.as_i64(), 0);
227 }
228
229 #[rstest]
230 fn test_into_from() {
231 let nanos: UnixNanos = 456.into();
232 let value: u64 = nanos.into();
233 assert_eq!(value, 456);
234 }
235
236 #[rstest]
237 fn test_from_str() {
238 let nanos: UnixNanos = "123".parse().unwrap();
239 assert_eq!(nanos.as_u64(), 123);
240 }
241
242 #[rstest]
243 fn test_from_str_invalid() {
244 let result = "abc".parse::<UnixNanos>();
245 assert!(result.is_err());
246 }
247
248 #[rstest]
249 fn test_try_from_datetime_valid() {
250 use chrono::TimeZone;
251 let datetime = Utc.timestamp_opt(1_000_000_000, 0).unwrap(); let nanos = UnixNanos::from(datetime);
253 assert_eq!(nanos.as_u64(), 1_000_000_000_000_000_000);
254 }
255
256 #[rstest]
257 fn test_eq() {
258 let nanos = UnixNanos::from(100);
259 assert_eq!(nanos, 100);
260 assert_eq!(nanos, Some(100));
261 assert_ne!(nanos, 200);
262 assert_ne!(nanos, Some(200));
263 assert_ne!(nanos, None);
264 }
265
266 #[rstest]
267 fn test_partial_cmp() {
268 let nanos = UnixNanos::from(100);
269 assert_eq!(nanos.partial_cmp(&100), Some(Ordering::Equal));
270 assert_eq!(nanos.partial_cmp(&200), Some(Ordering::Less));
271 assert_eq!(nanos.partial_cmp(&50), Some(Ordering::Greater));
272 assert_eq!(nanos.partial_cmp(&None), Some(Ordering::Greater));
273 }
274
275 #[rstest]
276 fn test_edge_case_max_value() {
277 let nanos = UnixNanos::from(u64::MAX);
278 assert_eq!(format!("{nanos}"), format!("{}", u64::MAX));
279 }
280
281 #[rstest]
282 fn test_display() {
283 let nanos = UnixNanos::from(123);
284 assert_eq!(format!("{nanos}"), "123");
285 }
286
287 #[rstest]
288 fn test_addition() {
289 let nanos1 = UnixNanos::from(100);
290 let nanos2 = UnixNanos::from(200);
291 let result = nanos1 + nanos2;
292 assert_eq!(result.as_u64(), 300);
293 }
294
295 #[rstest]
296 fn test_add_assign() {
297 let mut nanos = UnixNanos::from(100);
298 nanos += 50_u64;
299 assert_eq!(nanos.as_u64(), 150);
300 }
301
302 #[rstest]
303 fn test_subtraction() {
304 let nanos1 = UnixNanos::from(200);
305 let nanos2 = UnixNanos::from(100);
306 let result = nanos1 - nanos2;
307 assert_eq!(result.as_u64(), 100);
308 }
309
310 #[rstest]
311 fn test_sub_assign() {
312 let mut nanos = UnixNanos::from(200);
313 nanos -= 50_u64;
314 assert_eq!(nanos.as_u64(), 150);
315 }
316
317 #[rstest]
318 #[should_panic(expected = "Error adding with overflow")]
319 fn test_overflow_add() {
320 let nanos = UnixNanos::from(u64::MAX);
321 let _ = nanos + UnixNanos::from(1); }
323
324 #[rstest]
325 #[should_panic(expected = "Error adding with overflow")]
326 fn test_overflow_add_u64() {
327 let nanos = UnixNanos::from(u64::MAX);
328 let _ = nanos + 1_u64; }
330
331 #[rstest]
332 #[should_panic(expected = "Error subtracting with underflow")]
333 fn test_overflow_sub() {
334 let _ = UnixNanos::default() - UnixNanos::from(1); }
336
337 #[rstest]
338 #[should_panic(expected = "Error subtracting with underflow")]
339 fn test_overflow_sub_u64() {
340 let _ = UnixNanos::default() - 1_u64; }
342
343 #[rstest]
344 fn test_serde_json() {
345 let nanos = UnixNanos::from(123);
346 let json = serde_json::to_string(&nanos).unwrap();
347 let deserialized: UnixNanos = serde_json::from_str(&json).unwrap();
348 assert_eq!(deserialized, nanos);
349 }
350
351 #[rstest]
352 fn test_serde_edge_cases() {
353 let nanos = UnixNanos::from(u64::MAX);
354 let json = serde_json::to_string(&nanos).unwrap();
355 let deserialized: UnixNanos = serde_json::from_str(&json).unwrap();
356 assert_eq!(deserialized, nanos);
357 }
358}