greg/real/point.rs
1use std::time;
2use std::ops::{
3 Add,
4 Sub
5};
6
7use super::{
8 Point,
9 Span
10};
11
12// Constructors
13impl Point {
14 /// The earliest [`Point`] that can be represented by the timestamp
15 ///
16 /// `-292277022657-01-27 08:29:52` (a Monday)
17 pub const MIN: Self = Self::from_epoch(i64::MIN);
18 /// The last [`Point`] that can be represented by the timestamp
19 ///
20 /// `292277026596-12-04 15:30:07` (a Sunday)
21 pub const MAX: Self = Self::from_epoch(i64::MAX);
22
23 /// Same as constructing `Point {timestamp}` directly
24 pub const fn from_epoch(timestamp: i64) -> Self {
25 Self {timestamp}
26 }
27
28 /// Get the current [`Point`] in time from the system clock
29 pub fn now() -> Self {
30 let sys = time::SystemTime::now();
31 let timestamp = match sys.duration_since(time::UNIX_EPOCH) {
32 Ok(duration) => duration.as_secs() as i64,
33 Err(err) => -(err.duration().as_secs() as i64)
34 };
35 Self {timestamp}
36 }
37
38 /// Checked [`Span`] addition
39 ///
40 /// Computes `self + span`, returning `None` if overflow occurred.
41 ///
42 /// ```
43 /// use greg::{Point, Span};
44 ///
45 /// assert_eq!(
46 /// Point::now().checked_add(Span::parse("300000000000y")),
47 /// None
48 /// );
49 /// assert_eq!(
50 /// Point::MIN.checked_add(Span::MAX),
51 /// Some(Point::MAX)
52 /// );
53 /// ```
54 pub const fn checked_add(self, span: Span) -> Option<Self> {
55 match self.shift_to_u64().checked_add(span.seconds) {
56 Some(unsigned) => Some(Self::shift_from_u64(unsigned)),
57 None => None
58 }
59 }
60
61 /// Checked [`Span`] subtraction
62 ///
63 /// Computes `self - span`, returning `None` if overflow occurred.
64 ///
65 /// ```
66 /// use greg::{Point, Span};
67 ///
68 /// assert_eq!(
69 /// Point::now().checked_sub(Span::parse("300000000000y")),
70 /// None
71 /// );
72 ///
73 /// assert_eq!(
74 /// Point::MAX.checked_sub(Span::MAX),
75 /// Some(Point::MIN)
76 /// );
77 /// ```
78 pub const fn checked_sub(self, span: Span) -> Option<Self> {
79 match self.shift_to_u64().checked_sub(span.seconds) {
80 Some(unsigned) => Some(Self::shift_from_u64(unsigned)),
81 None => None
82 }
83 }
84
85 /// Saturating [`Span`] addition
86 ///
87 /// Computes `self + span`, saturating at the numeric bounds instead of overflowing.
88 ///
89 /// ```
90 /// use greg::{Point, Span};
91 ///
92 /// assert_eq!(
93 /// Point::now().saturating_add(Span::parse("300000000000y")),
94 /// Point::MAX
95 /// );
96 /// assert_eq!(
97 /// Point::MIN.saturating_add(Span::MAX - Span::SECOND),
98 /// Point::MAX - Span::SECOND
99 /// );
100 /// ```
101 pub const fn saturating_add(self, span: Span) -> Self {
102 Self::shift_from_u64(
103 self.shift_to_u64().saturating_add(span.seconds)
104 )
105 }
106
107 /// Saturating [`Span`] subtraction
108 ///
109 /// Computes `self - span`, saturating at the numeric bounds instead of overflowing.
110 ///
111 /// ```
112 /// use greg::{Point, Span};
113 ///
114 /// assert_eq!(
115 /// Point::now().saturating_sub(Span::parse("300000000000y")),
116 /// Point::MIN
117 /// );
118 ///
119 /// assert_eq!(
120 /// Point::MAX.saturating_sub(Span::MAX - Span::SECOND),
121 /// Point::MIN + Span::SECOND
122 /// );
123 /// ```
124 pub const fn saturating_sub(self, span: Span) -> Self {
125 Self::shift_from_u64(
126 self.shift_to_u64().saturating_sub(span.seconds)
127 )
128 }
129
130 /// Shift the timestamp into a `u64`
131 ///
132 /// We need to shift back & forth because the [`Span`] is a `u64`.
133 /// If we did the addition/subtraction in `i64`, we couldn't do `Point(i64::MAX) - Span(u64::MAX) = Point(i64::MIN)` in one go, for instance.
134 /// By doing it this way, we can add/subtract more than `i64::MAX` and only check for overflows at the end.
135 ///
136 /// Credit: https://stackoverflow.com/a/74491572
137 const fn shift_to_u64(self) -> u64 {
138 (self.timestamp as u64).wrapping_add(u64::MAX / 2 + 1)
139 }
140 /// Shift the `u64` back into a timestamp
141 ///
142 /// Credit: https://stackoverflow.com/a/74491572
143 const fn shift_from_u64(u: u64) -> Self {
144 Self { timestamp: u.wrapping_sub(u64::MAX / 2 + 1) as i64 }
145 }
146}
147
148// Math
149impl Add<Span> for Point {
150 type Output = Self;
151 fn add(self, rhs: Span) -> Self::Output {
152 let timestamp = self.timestamp + rhs.seconds as i64;
153 Self {timestamp}
154 }
155}
156
157impl Sub<Span> for Point {
158 type Output = Self;
159 fn sub(self, rhs: Span) -> Self::Output {
160 let timestamp = self.timestamp - rhs.seconds as i64;
161 Self {timestamp}
162 }
163}
164
165impl Sub<Point> for Point {
166 type Output = Span;
167 /// Note that [`Span`]s cannot be negative, so this is the **absolute** difference between the [`Point`]s
168 fn sub(self, rhs: Point) -> Self::Output {
169 let seconds = self.timestamp.abs_diff(rhs.timestamp);
170 Span {seconds}
171 }
172}
173
174impl From<time::SystemTime> for Point {
175 fn from(sys: time::SystemTime) -> Self {
176 let timestamp = match sys.duration_since(time::UNIX_EPOCH) {
177 Ok(duration) => duration.as_secs() as i64,
178 Err(err) => -(err.duration().as_secs() as i64)
179 };
180 Self {timestamp}
181 }
182}