greg 0.1.2

WIP: datetime library
Documentation
use std::fmt;
use std::num::ParseIntError;
use std::ops::{
	Add,
	Sub,
	Mul
};
use std::str::FromStr;

use super::{
	Scale,
	Span
};

#[derive(Debug, Clone, PartialEq, Eq)]
/// Error encountered parsing [`Span`]
pub enum ParseSpanError {
	/// Error encountered parsing [`u64`] number segment
	ParseInt(ParseIntError),
	/// Resulting number of seconds overflowed [`u64`]
	Overflow,
	/// Empty string is not a valid [`Span`]
	Empty,
	/// Units must be in order, from highest to lowest (though skipping is allowed).
	/// See [`Span`] for supported units.
	InvalidOrOutOfOrderUnit,
	/// Every integer must be followed immediately by a unit
	MissingUnit
}

// Constructors
impl Span {
	/// Same as constructing `Span {seconds}` directly
	pub const fn from_seconds(seconds: u64) -> Self {
		Self {seconds}
	}
}

#[allow(missing_docs)]
impl Span {
	pub const SCALES: [(u64, &'static str, Scale); 7] = [
		(31556952, "y",  Scale::Years),
		( 2629746, "mo", Scale::Months),
		(  604800, "w",  Scale::Weeks),
		(   86400, "d",  Scale::Days),
		(    3600, "h",  Scale::Hours),
		(      60, "m",  Scale::Minutes),
		(       1, "s",  Scale::Seconds)
	];
	pub const WEEK:   Self = Self::from_seconds(604800);
	pub const DAY:    Self = Self::from_seconds( 86400);
	pub const HOUR:   Self = Self::from_seconds(  3600);
	pub const MINUTE: Self = Self::from_seconds(    60);
	pub const SECOND: Self = Self::from_seconds(     1);

	pub const ZERO: Self = Self::from_seconds(0);
}

// Math
impl Span {
	/// Checked subtraction: computes `self - other`, returning `None` if overflow occurred.
	pub fn checked_sub(self, other: Self) -> Option<Self> {
		self.seconds
			.checked_sub(other.seconds)
			.map(Self::from_seconds)
	}

	/// Break down into a [`Scale`] and count
	///
	/// Returns the biggest [`Scale`] that divides the [`Span`] without a remainder as well as the result of this division.
	/// One exception is the empty [`Span::ZERO`], because the count is `0` for every [`Scale`].
	/// Instead of returning `(Scale::Years, 0)`, which would be counter-intuitive, the zero-second [`Span`] always returns the more natural `(Scale::Seconds, 0)`.
	///
	/// Remember that [`Scale::Years`] and [`Scale::Months`] are special, because they represent an *average*, they do not cleanly divide into weeks, days or even minutes!
	/// See the [`Scale`] documentation for details.
	/// ```
	/// use greg::{Span, Scale};
	///
	/// assert_eq!(Span::from_seconds(600).scale_div(), (Scale::Minutes, 10));
	/// assert_eq!(Span::ZERO.scale_div(), (Scale::Seconds, 0));
	/// let one_and_a_half: Span = "1h30m".parse().unwrap();
	/// assert_eq!(one_and_a_half.scale_div(), (Scale::Minutes, 90));
	///
	/// ```
	pub fn scale_div(self) -> (Scale, u64) {
		if self == Self::ZERO {return (Scale::Seconds, 0)}
		Self::SCALES.iter()
			.find(|(scale_sec, ..)| self.seconds % scale_sec == 0)
			.map(|&(scale_sec, _, scale)| (scale, self.seconds / scale_sec))
			.unwrap()
	}
}

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

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

impl Mul<u64> for Span {
	type Output = Self;
	fn mul(mut self, rhs: u64) -> Self::Output {
		self.seconds *= rhs;
		self
	}
}
impl From<Scale> for Span {
	fn from(scale: Scale) -> Self {Self::from_seconds(scale.as_seconds())}
}


impl FromStr for Span {
	type Err = ParseSpanError;
	/// Parse terse duration format
	///
	///```
	/// use greg::Span;
	/// assert_eq!("0s".parse::<Span>().unwrap().seconds, 0);
	/// assert_eq!("10s".parse::<Span>().unwrap().seconds, 10);
	/// assert_eq!("1m30s".parse::<Span>().unwrap().seconds, 90);
	/// "3mo2h10m30s".parse::<Span>().unwrap();
	/// "100000y".parse::<Span>().unwrap();
	///
	///```
	fn from_str(s: &str) -> Result<Self, Self::Err> {
		if s.is_empty() {return Err(ParseSpanError::Empty)}

		let mut seconds: u64 = 0;
		let mut to_parse = s;

		let mut scale_idx = 0;

		while !to_parse.is_empty() {
			let mut chars = to_parse.char_indices();
			let num_segment;
			let scale_segment;

			// Find where the number segment ends
			loop {
				let (i, c) = chars.next().ok_or(ParseSpanError::MissingUnit)?;
				if !c.is_ascii_digit() {
					num_segment = &to_parse[..i];
					break
				}
			}

			// Find where the scale segment ends
			loop { match chars.next() {
				Some((i, c)) if c.is_ascii_digit() => {
					scale_segment = &to_parse[num_segment.len()..i];
					to_parse = &to_parse[i..];
					break
				},
				Some(_) => continue,
				None => {
					scale_segment = &to_parse[num_segment.len()..];
					to_parse = "";
					break
				}
			}}

			let (new_idx, (scale, ..)) = Self::SCALES[scale_idx..]
				.iter()
				.enumerate()
				.find(|(_i, s)| s.1 == scale_segment)
				.ok_or(ParseSpanError::InvalidOrOutOfOrderUnit)?;
			seconds = scale
				.checked_mul(num_segment.parse()?)
				.and_then(|s| seconds.checked_add(s))
				.ok_or(ParseSpanError::Overflow)?;
			scale_idx += new_idx;
		}

		Ok(Self{seconds})
	}
}

impl fmt::Display for Span {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		if self.seconds == 0 {
			return "0s".fmt(f)
		}
		let mut seconds = self.seconds;
		let mut iter = Self::SCALES
			.iter()
			.filter_map(|(scale_seconds, scale_short, _)| {
				let remainder = seconds % scale_seconds;
				let scale_portion = seconds - remainder;
				seconds = remainder;
				(scale_portion > 0)
					.then_some(scale_portion / scale_seconds)
					.map(|scale_count| (scale_count, scale_short))
			})
			.take(f.precision().unwrap_or(Self::SCALES.len()));

		if f.alternate() {
			iter
				.next()
				.map(|(count, unit)| write!(f, "{count}{unit}"))
				.transpose()?;
			iter
				.map(|(count, unit)| write!(f, " {count}{unit}"))
				.reduce(Result::and)
				.transpose()
				.map(|_| ())
		}
		else {
			iter
				.map(|(count, unit)| write!(f, "{count}{unit}"))
				.reduce(Result::and)
				.transpose()
				.map(|_| ())
		}
	}
}
impl fmt::Debug for Span {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		fmt::Display::fmt(self, f)
	}
}

/*
 *	PARSE SPAN ERROR
 */

impl fmt::Display for ParseSpanError {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		match self {
			Self::ParseInt(e) => fmt::Display::fmt(e, f),
			Self::Overflow => f.write_str("duration too large"),
			Self::Empty => f.write_str("invalid empty duration"),
			Self::InvalidOrOutOfOrderUnit => f.write_str(
				"invalid or out of order unit"
			),
			Self::MissingUnit => f.write_str("invalid duration without unit")
		}
	}
}

impl From<ParseIntError> for ParseSpanError {
	fn from(e: ParseIntError) -> Self {Self::ParseInt(e)}
}


/*
 *	TESTS
 */

#[test]
fn parse_span() {
	let sec_per_year = Span::from(Scale::Years).seconds;
	let sec_per_month = Span::from(Scale::Months).seconds;

	let valid = [
		("0s", 0),
		("10s", 10),
		("1m30s", 90),
		("100000y", 100_000 * sec_per_year),
		("3mo2h10m30s", (3 * sec_per_month) + (2 * 60 * 60) + (10 * 60) + 30),
		("0y0mo0w0d0h0m0s", 0),
		("18446744073709551615s", 18446744073709551615)
	];

	let invalid = [
		"0",
		"10Y",
		"1y2m3d",
		"10s30h",
		"0y0mo0w0d0h0m0s0ns",
		"3 seconds",
		"3 minutes",
		"10min",
		"1h1d",
		"-30s",
		"abc",
		"",
		"99999999999999y",
		"1m18446744073709551615s",
		"18446744073709551616s",
		"0000000000000000000000000000000000000000000000000000000000000",
		"9999999999999999999999999999999999999999999999999999999999999",
		"\n",
		"🤔",
		"ymowdhms",
		"s0"
	];

	for (to_parse, expected) in valid {
		println!("Parsing '{to_parse}', expecting {expected}");
		assert_eq!(
			to_parse.parse(),
			Ok(Span::from_seconds(expected)),
			"failed to parse {to_parse}"
		);
	}

	for to_parse in invalid {
		println!("Parsing '{to_parse}', expecting error");
		let err = to_parse.parse::<Span>().unwrap_err();
		println!("Err: '{err}'!)");
	}
}