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}