From bd3445c23d771cf95e57a00cc7936a6212324079 Mon Sep 17 00:00:00 2001
From: Louis Capitanchik <contact@louiscap.co>
Date: Wed, 4 Jan 2023 15:17:40 +0000
Subject: [PATCH] Implement full Instant interface

---
 .gitignore             |   1 +
 Cargo.toml             |   2 +-
 src/instant_desktop.rs |  85 ++++++++++++++++++++++++++++++----
 src/instant_web.rs     | 102 +++++++++++++++++++++++++++++++++++------
 src/lib.rs             |   4 +-
 5 files changed, 167 insertions(+), 27 deletions(-)

diff --git a/.gitignore b/.gitignore
index 4fffb2f..3d84f13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 /target
 /Cargo.lock
+.idea/
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index 26ac5a7..9e2d4d4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "web_instant"
-version = "0.1.0"
+version = "0.2.0"
 edition = "2021"
 description = "Cross platform impl of Instant"
 authors = [
diff --git a/src/instant_desktop.rs b/src/instant_desktop.rs
index 8bbd5e0..73cf8b3 100644
--- a/src/instant_desktop.rs
+++ b/src/instant_desktop.rs
@@ -1,33 +1,98 @@
-use std::ops::Sub;
+use std::fmt::{Debug, Formatter};
+use std::ops::{Add, AddAssign, Sub, SubAssign};
 use std::time::{Duration, Instant};
 
-#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
+#[derive(Copy, Clone, PartialEq, PartialOrd)]
 pub struct Spot {
 	inner: Instant,
 }
 
+impl Debug for Spot {
+	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+		self.inner.fmt(f)
+	}
+}
+
 impl Spot {
 	pub fn now() -> Self {
 		Spot {
 			inner: Instant::now(),
 		}
 	}
-	pub fn as_secs(&self) -> u64 {
-		self.as_duration().as_secs()
+	/// Returns the amount of time elapsed since this instant was created.
+	pub fn elapsed(&self) -> Duration {
+		Spot::now() - *self
+	}
+	/// Returns the amount of time elapsed from another instant to this one,
+	/// or zero duration if that instant is later than this one.
+	pub fn duration_since(&self, earlier: Spot) -> Duration {
+		self.checked_duration_since(earlier).unwrap_or_default()
+	}
+	/// Returns the amount of time elapsed from another instant to this one,
+	/// or None if that instant is later than this one.
+	///
+	/// Due to [monotonicity bugs], even under correct logical ordering of the passed `Instant`s,
+	/// this method can return `None`.
+	pub fn checked_duration_since(&self, earlier: Spot) -> Option<Duration> {
+		self.inner.checked_duration_since(earlier.inner)
 	}
-	pub fn as_duration(&self) -> Duration {
-		self.inner.elapsed()
+	/// Returns the amount of time elapsed from another instant to this one,
+	/// or zero duration if that instant is later than this one.
+	pub fn saturating_duration_since(&self, earlier: Spot) -> Duration {
+		self.inner.saturating_duration_since(earlier.inner).unwrap_or_default()
 	}
+	/// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as
+	/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
+	/// otherwise.
+	pub fn checked_add(&self, duration: Duration) -> Option<Spot> {
+		self.inner.checked_add(duration).map(|inner| Spot { inner })
+	}
+	/// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as
+	/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
+	/// otherwise.
+	pub fn checked_sub(&self, duration: Duration) -> Option<Spot> {
+		self.inner.checked_sub(duration).map(|inner| Spot { inner })
+	}
+}
 
-	pub fn elapsed(&self) -> Duration {
-		self.as_duration()
+impl Add<Duration> for Spot {
+	type Output = Spot;
+
+	/// # Panics
+	///
+	/// This function may panic if the resulting point in time cannot be represented by the
+	/// underlying data structure. See [`Spot::checked_add`] for a version without panic.
+	fn add(self, other: Duration) -> Spot {
+		self.checked_add(other).expect("overflow when adding duration to instant")
+	}
+}
+
+impl AddAssign<Duration> for Spot {
+	fn add_assign(&mut self, other: Duration) {
+		*self = *self + other;
+	}
+}
+
+impl Sub<Duration> for Spot {
+	type Output = Spot;
+
+	fn sub(self, other: Duration) -> Spot {
+		self.checked_sub(other).expect("overflow when subtracting duration from instant")
+	}
+}
+
+impl SubAssign<Duration> for Spot {
+	fn sub_assign(&mut self, other: Duration) {
+		*self = *self - other;
 	}
 }
 
 impl Sub<Spot> for Spot {
 	type Output = Duration;
 
-	fn sub(self, rhs: Spot) -> Self::Output {
-		self.inner - rhs.inner
+	/// Returns the amount of time elapsed from another instant to this one,
+	/// or zero duration if that instant is later than this one.
+	fn sub(self, other: Spot) -> Duration {
+		self.duration_since(other)
 	}
 }
diff --git a/src/instant_web.rs b/src/instant_web.rs
index 36e892a..9a6439d 100644
--- a/src/instant_web.rs
+++ b/src/instant_web.rs
@@ -1,37 +1,111 @@
-use std::ops::Sub;
+use std::fmt::{Debug, Formatter};
+use std::ops::{Add, AddAssign, Sub, SubAssign};
 use std::time::Duration;
 
-#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
+#[derive(Copy, Clone, PartialEq, PartialOrd)]
 pub struct Spot {
 	/// Millisecond offset from the Unix Epoch - equivalent to Date.now()
 	inner: f64,
 }
 
+impl Debug for Spot {
+	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+		self.inner.fmt(f)
+	}
+}
+
 impl Spot {
 	pub fn now() -> Self {
 		Spot {
 			inner: js_sys::Date::now(),
 		}
 	}
-	pub fn as_secs(&self) -> u64 {
-		self.as_duration().as_secs()
+	/// Returns the amount of time elapsed since this instant was created.
+	pub fn elapsed(&self) -> Duration {
+		Spot::now() - *self
 	}
-	pub fn as_duration(&self) -> Duration {
-		Self::now() - *self
+	/// Returns the amount of time elapsed from another instant to this one,
+	/// or zero duration if that instant is later than this one.
+	pub fn duration_since(&self, earlier: Spot) -> Duration {
+		self.checked_duration_since(earlier).unwrap_or_default()
 	}
-	pub fn elapsed(&self) -> Duration {
-		self.as_duration()
+	/// Returns the amount of time elapsed from another instant to this one,
+	/// or None if that instant is later than this one.
+	///
+	/// Due to [monotonicity bugs], even under correct logical ordering of the passed `Instant`s,
+	/// this method can return `None`.
+	pub fn checked_duration_since(&self, earlier: Spot) -> Option<Duration> {
+		if earlier.inner > self.inner {
+			None
+		} else {
+			let millis = (self.inner - earlier.inner);
+			Some(Duration::from_secs_f64(millis * 1000.0))
+		}
+	}
+	/// Returns the amount of time elapsed from another instant to this one,
+	/// or zero duration if that instant is later than this one.
+	pub fn saturating_duration_since(&self, earlier: Spot) -> Duration {
+		self.checked_duration_since(earlier).unwrap_or_default()
+	}
+	/// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as
+	/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
+	/// otherwise.
+	pub fn checked_add(&self, duration: Duration) -> Option<Spot> {
+		let duration_millis = duration.as_secs_f64() / 1000.0;
+		Some(Spot { inner: self.inner + duration_millis })
+	}
+	/// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as
+	/// `Instant` (which means it's inside the bounds of the underlying data structure), `None`
+	/// otherwise.
+	pub fn checked_sub(&self, duration: Duration) -> Option<Spot> {
+		let duration_millis = duration.as_secs_f64() / 1000.0;
+		if duration_millis > self.inner {
+			None
+		} else {
+			Some(Spot { inner: self.inner - duration_millis })
+		}
+	}
+}
+
+impl Add<Duration> for Spot {
+	type Output = Spot;
+
+	/// # Panics
+	///
+	/// This function may panic if the resulting point in time cannot be represented by the
+	/// underlying data structure. See [`Spot::checked_add`] for a version without panic.
+	fn add(self, other: Duration) -> Spot {
+		self.checked_add(other).expect("overflow when adding duration to instant")
+	}
+}
+
+impl AddAssign<Duration> for Spot {
+	fn add_assign(&mut self, other: Duration) {
+		*self = *self + other;
 	}
 }
 
+impl Sub<Duration> for Spot {
+	type Output = Spot;
+
+	fn sub(self, other: Duration) -> Spot {
+		self.checked_sub(other).expect("overflow when subtracting duration from instant")
+	}
+}
+
+impl SubAssign<Duration> for Spot {
+	fn sub_assign(&mut self, other: Duration) {
+		*self = *self - other;
+	}
+}
+
+
 impl Sub<Spot> for Spot {
 	type Output = Duration;
 
-	fn sub(self, rhs: Spot) -> Self::Output {
-		let diff = (self.inner - rhs.inner).max(0.0);
-		let secs = (diff as u64) / 1_000;
-		let nanos = (((diff as u64) % 1_000) as u32) * 1_000_000;
-
-		Duration::new(secs, nanos)
+	/// Returns the amount of time elapsed from another instant to this one,
+	/// or zero duration if that instant is later than this one.
+	fn sub(self, other: Spot) -> Duration {
+		self.duration_since(other)
 	}
 }
diff --git a/src/lib.rs b/src/lib.rs
index cda40fc..409aa99 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,6 @@
-#[cfg(not(target_arch = "wasm32"))]
+#[cfg(not(target_family = "wasm"))]
 mod instant_desktop;
-#[cfg(target_arch = "wasm32")]
+// #[cfg(target_family = "wasm")]
 mod instant_web;
 
 #[cfg(not(target_arch = "wasm32"))]
-- 
GitLab