Skip to main content

proto_types/timestamp/
timestamp_operations.rs

1use core::{
2	cmp::Ordering,
3	ops::{Add, Sub},
4	time::Duration as StdDuration,
5};
6
7use crate::{Duration, Timestamp};
8
9impl<'b> Sub<&'b Duration> for &Timestamp {
10	type Output = Timestamp;
11
12	fn sub(self, rhs: &'b Duration) -> Self::Output {
13		let duration = rhs.normalized();
14
15		let mut new = Timestamp {
16			seconds: self.seconds.saturating_sub(duration.seconds),
17			nanos: self.nanos - duration.nanos,
18		};
19
20		new.normalize();
21
22		new
23	}
24}
25
26impl Sub<StdDuration> for Timestamp {
27	type Output = Self;
28
29	fn sub(self, rhs: StdDuration) -> Self::Output {
30		let mut new = Self {
31			seconds: self
32				.seconds
33				.saturating_sub(rhs.as_secs().cast_signed()),
34			nanos: self
35				.nanos
36				.saturating_sub(rhs.subsec_nanos().cast_signed()),
37		};
38
39		new.normalize();
40
41		new
42	}
43}
44
45#[cfg(feature = "chrono")]
46impl Sub<chrono::TimeDelta> for Timestamp {
47	type Output = Self;
48
49	fn sub(self, rhs: chrono::TimeDelta) -> Self::Output {
50		let mut new = Self {
51			seconds: self.seconds.saturating_sub(rhs.num_seconds()),
52			nanos: self.nanos.saturating_sub(rhs.subsec_nanos()),
53		};
54
55		new.normalize();
56
57		new
58	}
59}
60
61impl Sub<Duration> for Timestamp {
62	type Output = Self;
63	#[inline]
64	fn sub(self, rhs: Duration) -> Self::Output {
65		<&Self as Sub<&Duration>>::sub(&self, &rhs)
66	}
67}
68
69impl<'b> Sub<&'b Duration> for Timestamp {
70	type Output = Self;
71	#[inline]
72	fn sub(self, rhs: &'b Duration) -> Self::Output {
73		<&Self as Sub<&Duration>>::sub(&self, rhs)
74	}
75}
76
77impl<'a> Sub<Duration> for &'a Timestamp {
78	type Output = Timestamp;
79	#[inline]
80	fn sub(self, rhs: Duration) -> Self::Output {
81		<&'a Timestamp as Sub<&Duration>>::sub(self, &rhs)
82	}
83}
84
85impl<'b> Add<&'b Duration> for &Timestamp {
86	type Output = Timestamp;
87
88	fn add(self, rhs: &'b Duration) -> Self::Output {
89		let duration = rhs.normalized();
90
91		let mut new = Timestamp {
92			seconds: self.seconds.saturating_add(duration.seconds),
93			nanos: self.nanos + duration.nanos,
94		};
95
96		new.normalize();
97
98		new
99	}
100}
101
102impl Add<StdDuration> for Timestamp {
103	type Output = Self;
104
105	fn add(self, rhs: StdDuration) -> Self::Output {
106		let mut new = Self {
107			seconds: self
108				.seconds
109				.saturating_add(rhs.as_secs().cast_signed()),
110			nanos: self
111				.nanos
112				.saturating_add(rhs.subsec_nanos().cast_signed()),
113		};
114
115		new.normalize();
116
117		new
118	}
119}
120
121#[cfg(feature = "chrono")]
122impl Add<chrono::TimeDelta> for Timestamp {
123	type Output = Self;
124
125	fn add(self, rhs: chrono::TimeDelta) -> Self::Output {
126		let mut new = Self {
127			seconds: self.seconds.saturating_add(rhs.num_seconds()),
128			nanos: self.nanos.saturating_add(rhs.subsec_nanos()),
129		};
130
131		new.normalize();
132
133		new
134	}
135}
136
137impl<'b> Add<&'b Duration> for Timestamp {
138	type Output = Self;
139	#[inline]
140	fn add(self, rhs: &'b Duration) -> Self::Output {
141		<&Self as Add<&Duration>>::add(&self, rhs)
142	}
143}
144
145impl Add<Duration> for &Timestamp {
146	type Output = Timestamp;
147	#[inline]
148	fn add(self, rhs: Duration) -> Self::Output {
149		<Self as Add<&Duration>>::add(self, &rhs)
150	}
151}
152
153impl Add<Duration> for Timestamp {
154	type Output = Self;
155
156	#[inline]
157	fn add(self, rhs: Duration) -> Self::Output {
158		&self + &rhs
159	}
160}
161
162impl PartialOrd for Timestamp {
163	#[inline]
164	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
165		Some(self.cmp(other))
166	}
167}
168
169impl Ord for Timestamp {
170	#[inline]
171	fn cmp(&self, other: &Self) -> Ordering {
172		(self.seconds, self.nanos).cmp(&(other.seconds, other.nanos))
173	}
174}
175
176#[cfg(test)]
177mod tests {
178	use super::*;
179
180	macro_rules! get_duration {
181		(duration, $secs:literal, $nanos:literal) => {
182			Duration::new($secs, $nanos)
183		};
184
185		(std, $secs:literal, $nanos:literal) => {
186			StdDuration::new($secs, $nanos)
187		};
188
189		(chrono, $secs:literal, $nanos:literal) => {
190			TimeDelta::new($secs, $nanos).unwrap()
191		};
192	}
193
194	macro_rules! test_ops {
195		($duration:ident) => {
196			#[test]
197			fn test_simple_addition() {
198				let t = Timestamp::new(100, 500);
199				let d = get_duration!($duration, 50, 100);
200				let res = t + d;
201				assert_eq!(res, Timestamp::new(150, 600));
202			}
203
204			#[test]
205			fn test_nano_overflow_addition() {
206				let t = Timestamp::new(100, 900_000_000);
207				let d = get_duration!($duration, 0, 200_000_000);
208				let res = t + d;
209				// 900M + 200M = 1.1B -> 1s + 100M
210				assert_eq!(res, Timestamp::new(101, 100_000_000));
211			}
212
213			#[test]
214			fn test_simple_subtraction() {
215				let t = Timestamp::new(100, 500);
216				let d = get_duration!($duration, 50, 100);
217				let res = t - d;
218				assert_eq!(res, Timestamp::new(50, 400));
219			}
220
221			#[test]
222			fn test_subtraction_crossing_zero() {
223				let t = Timestamp::new(100, 100);
224				let d = get_duration!($duration, 200, 0);
225				let res = t - d;
226				// 100 - 200 = -100
227				assert_eq!(res, Timestamp::new(-100, 100));
228			}
229
230			#[test]
231			fn test_subtraction_borrowing_nanos() {
232				// Case: (10s, 100ns) - (0s, 200ns)
233				// Raw: 10s, -100ns
234				// Normalized: 9s, 999_999_900ns
235				let t = Timestamp::new(10, 100);
236				let d = get_duration!($duration, 0, 200);
237				let res = t - d;
238				assert_eq!(res, Timestamp::new(9, 999_999_900));
239			}
240
241			#[test]
242			fn test_add_saturation_max() {
243				// i64::MAX + 1s should stay at MAX, NOT wrap to MIN
244				let t = Timestamp::new(i64::MAX, 0);
245				let d = get_duration!($duration, 1, 0);
246				let res = t + d;
247
248				assert_eq!(res.seconds, i64::MAX);
249			}
250
251			#[test]
252			fn test_sub_saturation_min() {
253				// i64::MIN - 1s should stay at MIN, NOT wrap to MAX
254				let t = Timestamp::new(i64::MIN, 0);
255				let d = get_duration!($duration, 1, 0);
256				let res = t - d;
257
258				assert_eq!(res.seconds, i64::MIN);
259			}
260		};
261	}
262
263	macro_rules! test_saturation {
264		($duration:ident) => {
265			#[test]
266			fn test_add_saturation_with_nanos() {
267				// i64::MAX + (0s, 2B nanos) -> i64::MAX + 2s -> Saturation
268				let t = Timestamp::new(i64::MAX, 0);
269				// Duration > 1s via nanos
270				let d = get_duration!($duration, 0, 2_000_000_000);
271				let res = t + d;
272
273				assert_eq!(res.seconds, i64::MAX);
274			}
275		};
276	}
277
278	#[cfg(feature = "chrono")]
279	mod chrono_test {
280		use super::*;
281
282		use chrono::TimeDelta;
283
284		test_ops!(chrono);
285	}
286
287	test_ops!(duration);
288	test_saturation!(duration);
289
290	#[test]
291	fn test_sub_double_negative_saturation() {
292		// i64::MAX - (-1s) is effectively i64::MAX + 1s -> Should saturate at MAX
293		let t = Timestamp::new(i64::MAX, 0);
294		let d = Duration::new(-1, 0);
295		let res = t - d;
296
297		assert_eq!(res.seconds, i64::MAX);
298	}
299
300	#[test]
301	fn test_add_negative_duration() {
302		let t = Timestamp::new(100, 0);
303		let d = Duration::new(-50, 0);
304		let res = t + d;
305		assert_eq!(res, Timestamp::new(50, 0));
306	}
307
308	mod std_duration {
309		use super::*;
310
311		test_ops!(std);
312		test_saturation!(std);
313	}
314}