1use core::ops::{Add, AddAssign, Sub, SubAssign};
6use core::time::Duration;
7
8use crate::types::{BlockHeight, TimePoint};
9
10const fn duration_nanos(duration: Duration) -> Option<u64> {
13 let nanos = duration.as_nanos();
14 if nanos > u64::MAX as u128 {
15 None
16 } else {
17 Some(nanos as u64)
18 }
19}
20
21impl TimePoint {
22 pub const UNIX_EPOCH: TimePoint = TimePoint::new(0);
29
30 pub fn checked_add(&self, duration: Duration) -> Option<TimePoint> {
33 duration_nanos(duration)
34 .and_then(|nanos| (**self).checked_add(nanos))
35 .map(TimePoint::new)
36 }
37
38 pub fn checked_sub(&self, duration: Duration) -> Option<TimePoint> {
40 duration_nanos(duration)
41 .and_then(|nanos| (**self).checked_sub(nanos))
42 .map(TimePoint::new)
43 }
44
45 pub fn saturating_add(&self, duration: Duration) -> TimePoint {
51 match self.checked_add(duration) {
52 Some(sum) => sum,
53 None => TimePoint::new(u64::MAX),
54 }
55 }
56
57 pub fn saturating_sub(&self, duration: Duration) -> TimePoint {
60 match self.checked_sub(duration) {
61 Some(difference) => difference,
62 None => TimePoint::UNIX_EPOCH,
63 }
64 }
65
66 pub fn duration_since(&self, earlier: &TimePoint) -> Duration {
71 Duration::from_nanos((**self).saturating_sub(**earlier))
72 }
73
74 pub fn checked_duration_since(
77 &self,
78 earlier: &TimePoint,
79 ) -> Option<Duration> {
80 (**self).checked_sub(**earlier).map(Duration::from_nanos)
81 }
82}
83
84impl Add<Duration> for &TimePoint {
85 type Output = TimePoint;
86
87 fn add(self, rhs: Duration) -> TimePoint {
88 self.checked_add(rhs)
89 .expect("overflow when adding duration to timepoint")
90 }
91}
92
93impl Add<Duration> for TimePoint {
94 type Output = TimePoint;
95
96 fn add(self, rhs: Duration) -> TimePoint {
97 &self + rhs
98 }
99}
100
101impl AddAssign<Duration> for TimePoint {
102 fn add_assign(&mut self, rhs: Duration) {
103 *self = &*self + rhs;
104 }
105}
106
107impl Sub<Duration> for &TimePoint {
108 type Output = TimePoint;
109
110 fn sub(self, rhs: Duration) -> TimePoint {
111 self.checked_sub(rhs)
112 .expect("overflow when subtracting duration from timepoint")
113 }
114}
115
116impl Sub<Duration> for TimePoint {
117 type Output = TimePoint;
118
119 fn sub(self, rhs: Duration) -> TimePoint {
120 &self - rhs
121 }
122}
123
124impl SubAssign<Duration> for TimePoint {
125 fn sub_assign(&mut self, rhs: Duration) {
126 *self = &*self - rhs;
127 }
128}
129
130impl Sub<&TimePoint> for &TimePoint {
131 type Output = Duration;
132
133 fn sub(self, rhs: &TimePoint) -> Duration {
134 self.duration_since(rhs)
135 }
136}
137
138impl Sub<TimePoint> for &TimePoint {
139 type Output = Duration;
140
141 fn sub(self, rhs: TimePoint) -> Duration {
142 self.duration_since(&rhs)
143 }
144}
145
146impl Sub<&TimePoint> for TimePoint {
147 type Output = Duration;
148
149 fn sub(self, rhs: &TimePoint) -> Duration {
150 self.duration_since(rhs)
151 }
152}
153
154impl Sub<TimePoint> for TimePoint {
155 type Output = Duration;
156
157 fn sub(self, rhs: TimePoint) -> Duration {
158 self.duration_since(&rhs)
159 }
160}
161
162impl BlockHeight {
163 pub fn checked_add(&self, blocks: i64) -> Option<BlockHeight> {
165 (**self).checked_add(blocks).map(BlockHeight::new)
166 }
167
168 pub fn checked_sub(&self, blocks: i64) -> Option<BlockHeight> {
170 (**self).checked_sub(blocks).map(BlockHeight::new)
171 }
172
173 pub fn blocks_since(&self, earlier: &BlockHeight) -> i64 {
179 match self.checked_blocks_since(earlier) {
180 Some(blocks) => blocks,
181 None => panic!("overflow when subtracting block heights"),
182 }
183 }
184
185 pub fn checked_blocks_since(&self, earlier: &BlockHeight) -> Option<i64> {
188 (**self).checked_sub(**earlier)
189 }
190}
191
192impl Add<i64> for &BlockHeight {
193 type Output = BlockHeight;
194
195 fn add(self, rhs: i64) -> BlockHeight {
196 self.checked_add(rhs)
197 .expect("overflow when adding blocks to height")
198 }
199}
200
201impl Add<i64> for BlockHeight {
202 type Output = BlockHeight;
203
204 fn add(self, rhs: i64) -> BlockHeight {
205 &self + rhs
206 }
207}
208
209impl AddAssign<i64> for BlockHeight {
210 fn add_assign(&mut self, rhs: i64) {
211 *self = &*self + rhs;
212 }
213}
214
215impl Sub<i64> for &BlockHeight {
216 type Output = BlockHeight;
217
218 fn sub(self, rhs: i64) -> BlockHeight {
219 self.checked_sub(rhs)
220 .expect("overflow when subtracting blocks from height")
221 }
222}
223
224impl Sub<i64> for BlockHeight {
225 type Output = BlockHeight;
226
227 fn sub(self, rhs: i64) -> BlockHeight {
228 &self - rhs
229 }
230}
231
232impl SubAssign<i64> for BlockHeight {
233 fn sub_assign(&mut self, rhs: i64) {
234 *self = &*self - rhs;
235 }
236}
237
238impl Sub<&BlockHeight> for &BlockHeight {
239 type Output = i64;
240
241 fn sub(self, rhs: &BlockHeight) -> i64 {
242 self.blocks_since(rhs)
243 }
244}
245
246impl Sub<BlockHeight> for &BlockHeight {
247 type Output = i64;
248
249 fn sub(self, rhs: BlockHeight) -> i64 {
250 self.blocks_since(&rhs)
251 }
252}
253
254impl Sub<&BlockHeight> for BlockHeight {
255 type Output = i64;
256
257 fn sub(self, rhs: &BlockHeight) -> i64 {
258 self.blocks_since(rhs)
259 }
260}
261
262impl Sub<BlockHeight> for BlockHeight {
263 type Output = i64;
264
265 fn sub(self, rhs: BlockHeight) -> i64 {
266 self.blocks_since(&rhs)
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 const DAY: Duration = Duration::from_hours(24);
275
276 fn timepoint(nanos: u64) -> TimePoint {
277 TimePoint::new(nanos)
278 }
279
280 fn height(blocks: i64) -> BlockHeight {
281 BlockHeight::new(blocks)
282 }
283
284 #[test]
285 fn timepoint_plus_and_minus_duration() {
286 let now = timepoint(1_000);
287 let deadline = &now + Duration::from_nanos(7);
288 assert_eq!(deadline, timepoint(1_007));
289 assert!(now < deadline);
290 assert_eq!(&deadline - Duration::from_nanos(7), now);
291 assert_eq!(deadline.clone() - Duration::from_nanos(7), now);
292
293 let mut moving = now.clone();
294 moving += Duration::from_nanos(10);
295 moving -= Duration::from_nanos(3);
296 assert_eq!(moving, timepoint(1_007));
297
298 assert_eq!(
299 TimePoint::UNIX_EPOCH + Duration::from_secs(5),
300 timepoint(5_000_000_000)
301 );
302 assert_eq!(
305 &now + 7 * DAY,
306 timepoint(1_000 + 7 * 86_400 * 1_000_000_000)
307 );
308 }
309
310 #[test]
311 fn timepoint_difference_saturates_at_zero() {
312 let start = timepoint(100);
313 let end = timepoint(250);
314 assert_eq!(&end - &start, Duration::from_nanos(150));
315 assert_eq!(end.clone() - &start, Duration::from_nanos(150));
316 assert_eq!(&end - start.clone(), Duration::from_nanos(150));
317 assert_eq!(end.clone() - start.clone(), Duration::from_nanos(150));
318 assert_eq!(end.duration_since(&start), Duration::from_nanos(150));
319 assert_eq!(
320 end.checked_duration_since(&start),
321 Some(Duration::from_nanos(150))
322 );
323
324 assert_eq!(&start - &end, Duration::ZERO);
325 assert_eq!(start.duration_since(&end), Duration::ZERO);
326 assert_eq!(start.checked_duration_since(&end), None);
327 }
328
329 #[test]
330 fn timepoint_checked_and_saturating_forms() {
331 let late = timepoint(u64::MAX - 1);
332 assert_eq!(
333 late.checked_add(Duration::from_nanos(1)),
334 Some(timepoint(u64::MAX))
335 );
336 assert_eq!(late.checked_add(Duration::from_nanos(2)), None);
337 assert_eq!(late.saturating_add(DAY), timepoint(u64::MAX));
338 assert_eq!(timepoint(0).checked_add(Duration::MAX), None);
340 assert_eq!(
341 timepoint(0).saturating_add(Duration::MAX),
342 timepoint(u64::MAX)
343 );
344
345 let early = timepoint(1);
346 assert_eq!(
347 early.checked_sub(Duration::from_nanos(1)),
348 Some(TimePoint::UNIX_EPOCH)
349 );
350 assert_eq!(early.checked_sub(Duration::from_nanos(2)), None);
351 assert_eq!(early.saturating_sub(DAY), TimePoint::UNIX_EPOCH);
352 assert_eq!(early.checked_sub(Duration::MAX), None);
353 }
354
355 #[test]
356 #[should_panic(expected = "overflow when adding duration to timepoint")]
357 fn timepoint_add_overflow_panics() {
358 let _ = timepoint(u64::MAX) + Duration::from_nanos(1);
359 }
360
361 #[test]
362 #[should_panic(
363 expected = "overflow when subtracting duration from timepoint"
364 )]
365 fn timepoint_sub_before_epoch_panics() {
366 let _ = TimePoint::UNIX_EPOCH - Duration::from_nanos(1);
367 }
368
369 #[test]
370 fn height_plus_and_minus_blocks() {
371 let now = height(1_000);
372 let expiry = &now + 100;
373 assert_eq!(expiry, height(1_100));
374 assert!(now < expiry);
375 assert_eq!(&expiry - 100, now);
376 assert_eq!(expiry.clone() - 100, now);
377
378 let mut moving = now.clone();
379 moving += 10;
380 moving -= 3;
381 assert_eq!(moving, height(1_007));
382 }
383
384 #[test]
385 fn height_difference_is_signed() {
386 let earlier = height(100);
387 let later = height(250);
388 assert_eq!(&later - &earlier, 150);
389 assert_eq!(later.clone() - &earlier, 150);
390 assert_eq!(&later - earlier.clone(), 150);
391 assert_eq!(later.clone() - earlier.clone(), 150);
392 assert_eq!(later.blocks_since(&earlier), 150);
393 assert_eq!(&earlier - &later, -150);
394 assert_eq!(earlier.checked_blocks_since(&later), Some(-150));
395 assert_eq!(height(i64::MAX).checked_blocks_since(&height(-1)), None);
396 }
397
398 #[test]
399 fn height_checked_forms() {
400 let top = height(i64::MAX);
401 assert_eq!(top.checked_add(1), None);
402 assert_eq!(top.checked_sub(1), Some(height(i64::MAX - 1)));
403 assert_eq!(height(i64::MIN).checked_sub(1), None);
404 }
405
406 #[test]
407 #[should_panic(expected = "overflow when adding blocks to height")]
408 fn height_add_overflow_panics() {
409 let _ = height(i64::MAX) + 1;
410 }
411
412 #[test]
413 #[should_panic(expected = "overflow when subtracting block heights")]
414 fn height_difference_overflow_panics() {
415 let _ = height(i64::MAX) - height(-1);
416 }
417}