greg 0.2.5

Simple Unobtrusive Date & Time library
Documentation
use std::time;
use std::ops::{
	Add,
	Sub
};

use super::{
	Point,
	Span
};

// Constructors
impl Point {
	/// The earliest [`Point`] that can be represented by the timestamp
	///
	/// `-292277022657-01-27 08:29:52` (a Monday)
	pub const MIN: Self = Self::from_epoch(i64::MIN);
	/// The last [`Point`] that can be represented by the timestamp
	///
	/// `292277026596-12-04 15:30:07` (a Sunday)
	pub const MAX: Self = Self::from_epoch(i64::MAX);

	/// Same as constructing `Point {timestamp}` directly
	pub const fn from_epoch(timestamp: i64) -> Self {
		Self {timestamp}
	}

	/// Get the current [`Point`] in time from the system clock
	pub fn now() -> Self {
		let sys = time::SystemTime::now();
		let timestamp = match sys.duration_since(time::UNIX_EPOCH) {
			Ok(duration) => duration.as_secs() as i64,
			Err(err) => -(err.duration().as_secs() as i64)
		};
		Self {timestamp}
	}

	/// Checked [`Span`] addition
	///
	/// Computes `self + span`, returning `None` if overflow occurred.
	///
	/// ```
	/// use greg::{Point, Span};
	///
	/// assert_eq!(
	///     Point::now().checked_add(Span::parse("300000000000y")),
	///     None
	/// );
	/// assert_eq!(
	///     Point::MIN.checked_add(Span::MAX),
	///     Some(Point::MAX)
	/// );
	/// ```
	pub const fn checked_add(self, span: Span) -> Option<Self> {
		match self.shift_to_u64().checked_add(span.seconds) {
			Some(unsigned) => Some(Self::shift_from_u64(unsigned)),
			None => None
		}
	}

	/// Checked [`Span`] subtraction
	///
	/// Computes `self - span`, returning `None` if overflow occurred.
	///
	/// ```
	/// use greg::{Point, Span};
	///
	/// assert_eq!(
	///     Point::now().checked_sub(Span::parse("300000000000y")),
	///     None
	/// );
	///
	/// assert_eq!(
	///     Point::MAX.checked_sub(Span::MAX),
	///     Some(Point::MIN)
	/// );
	/// ```
	pub const fn checked_sub(self, span: Span) -> Option<Self> {
		match self.shift_to_u64().checked_sub(span.seconds) {
			Some(unsigned) => Some(Self::shift_from_u64(unsigned)),
			None => None
		}
	}

	/// Saturating [`Span`] addition
	///
	/// Computes `self + span`, saturating at the numeric bounds instead of overflowing.
	///
	/// ```
	/// use greg::{Point, Span};
	///
	/// assert_eq!(
	///     Point::now().saturating_add(Span::parse("300000000000y")),
	///     Point::MAX
	/// );
	/// assert_eq!(
	///     Point::MIN.saturating_add(Span::MAX - Span::SECOND),
	///     Point::MAX - Span::SECOND
	/// );
	/// ```
	pub const fn saturating_add(self, span: Span) -> Self {
		Self::shift_from_u64(
			self.shift_to_u64().saturating_add(span.seconds)
		)
	}

	/// Saturating [`Span`] subtraction
	///
	/// Computes `self - span`, saturating at the numeric bounds instead of overflowing.
	///
	/// ```
	/// use greg::{Point, Span};
	///
	/// assert_eq!(
	///     Point::now().saturating_sub(Span::parse("300000000000y")),
	///     Point::MIN
	/// );
	///
	/// assert_eq!(
	///     Point::MAX.saturating_sub(Span::MAX - Span::SECOND),
	///     Point::MIN + Span::SECOND
	/// );
	/// ```
	pub const fn saturating_sub(self, span: Span) -> Self {
		Self::shift_from_u64(
			self.shift_to_u64().saturating_sub(span.seconds)
		)
	}

	/// Shift the timestamp into a `u64`
	///
	/// We need to shift back & forth because the [`Span`] is a `u64`.
	/// 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.
	/// By doing it this way, we can add/subtract more than `i64::MAX` and only check for overflows at the end.
	///
	/// Credit: https://stackoverflow.com/a/74491572
	const fn shift_to_u64(self) -> u64 {
		(self.timestamp as u64).wrapping_add(u64::MAX / 2 + 1)
	}
	/// Shift the `u64` back into a timestamp
	///
	/// Credit: https://stackoverflow.com/a/74491572
	const fn shift_from_u64(u: u64) -> Self {
		Self { timestamp: u.wrapping_sub(u64::MAX / 2 + 1) as i64 }
	}
}

// Math
impl Add<Span> for Point {
	type Output = Self;
	fn add(self, rhs: Span) -> Self::Output {
		let timestamp = self.timestamp + rhs.seconds as i64;
		Self {timestamp}
	}
}

impl Sub<Span> for Point {
	type Output = Self;
	fn sub(self, rhs: Span) -> Self::Output {
		let timestamp = self.timestamp - rhs.seconds as i64;
		Self {timestamp}
	}
}

impl Sub<Point> for Point {
	type Output = Span;
	/// Note that [`Span`]s cannot be negative, so this is the **absolute** difference between the [`Point`]s
	fn sub(self, rhs: Point) -> Self::Output {
		let seconds = self.timestamp.abs_diff(rhs.timestamp);
		Span {seconds}
	}
}

impl From<time::SystemTime> for Point {
	fn from(sys: time::SystemTime) -> Self {
		let timestamp = match sys.duration_since(time::UNIX_EPOCH) {
			Ok(duration) => duration.as_secs() as i64,
			Err(err) => -(err.duration().as_secs() as i64)
		};
		Self {timestamp}
	}
}