use std::fmt;
use std::ops::{
Add,
Sub,
Mul,
Div,
Rem
};
use std::str::FromStr;
use super::{
Scale,
Span
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseSpanError {
Overflow,
Empty,
OutOfOrderUnit,
InvalidUnit,
MissingAmount,
MissingUnit
}
impl Span {
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);
}
impl Span {
pub const fn checked_add(self, other: Self) -> Option<Self> {
match self.seconds.checked_add(other.seconds) {
Some(s) => Some(Self::from_seconds(s)),
None => None
}
}
pub const fn checked_sub(self, other: Self) -> Option<Self> {
match self.seconds.checked_sub(other.seconds) {
Some(s) => Some(Self::from_seconds(s)),
None => None
}
}
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 Div<u64> for Span {
type Output = u64;
fn div(self, rhs: u64) -> Self::Output {
self.seconds / rhs
}
}
impl Div<Scale> for Span {
type Output = u64;
fn div(self, rhs: Scale) -> Self::Output {
self.seconds / rhs.as_seconds()
}
}
impl Rem<Scale> for Span {
type Output = Self;
fn rem(self, rhs: Scale) -> Self::Output {
Self {seconds: self.seconds % rhs.as_seconds()}
}
}
impl From<Scale> for Span {
fn from(scale: Scale) -> Self {Self::from_seconds(scale.as_seconds())}
}
impl Span {
#[must_use]
pub const fn parse(from: &str) -> Self {
match Self::try_parse(from) {
Ok(span) => span,
Err(err) => panic!("{}", err.as_str())
}
}
pub const fn try_parse(from: &str) -> Result<Self, ParseSpanError> {
let mut remaining_units = Self::SCALES.len();
let mut seconds: u64 = 0;
let mut bytes = from.as_bytes();
let mut current: Option<u64> = None;
if from.is_empty() {
return Err(ParseSpanError::Empty);
}
loop {
let (unit, rest) = match bytes {
[n @ b'0'..=b'9', rest @ ..] => {
let new_digit = (*n - b'0') as u64;
current = match current {
Some(n) => match n.checked_mul(10) {
Some(n) => match n.checked_add(new_digit) {
Some(n) => Some(n),
None => return Err(ParseSpanError::Overflow)
},
None => return Err(ParseSpanError::Overflow)
},
None => Some(new_digit)
};
bytes = rest;
continue;
},
[b'y', rest @ ..] => (6, rest),
[b'm', b'o', rest @ ..] => (5, rest),
[b'w', rest @ ..] => (4, rest),
[b'd', rest @ ..] => (3, rest),
[b'h', rest @ ..] => (2, rest),
[b'm', rest @ ..] => (1, rest),
[b's', rest @ ..] => (0, rest),
[_, ..] => return Err(ParseSpanError::InvalidUnit),
[] if current.is_none() => break,
[] => return Err(ParseSpanError::MissingUnit)
};
if remaining_units <= unit {
return Err(ParseSpanError::OutOfOrderUnit);
}
let Some(num) = current else {
return Err(ParseSpanError::MissingAmount);
};
let Some(mul) = num.checked_mul(Self::SCALES[6 - unit].0) else {
return Err(ParseSpanError::Overflow);
};
let Some(add) = seconds.checked_add(mul) else {
return Err(ParseSpanError::Overflow);
};
seconds = add;
remaining_units = unit;
current = None;
bytes = rest;
}
Ok(Self {seconds})
}
}
impl FromStr for Span {
type Err = ParseSpanError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_parse(s)
}
}
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)
}
}
impl ParseSpanError {
const fn as_str(&self) -> &'static str {
match self {
Self::Overflow => "duration too large",
Self::Empty => "invalid empty duration",
Self::InvalidUnit => "invalid unit",
Self::OutOfOrderUnit => "invalid unit order",
Self::MissingAmount => "invalid unit without amount",
Self::MissingUnit => "invalid amount without unit"
}
}
}
impl fmt::Display for ParseSpanError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
#[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}"
);
assert_eq!(Span::parse(to_parse), Span::from_seconds(expected));
}
for to_parse in invalid {
println!("Parsing '{to_parse}', expecting error");
let err = to_parse.parse::<Span>().unwrap_err();
println!("Err: '{err}'!)");
let err = Span::try_parse(to_parse).unwrap_err();
println!("Err: '{err}'!)");
}
}
#[test]
fn math() {
let span = Span::parse("1w2d3h4m5s");
assert_eq!(span % Scale::Days, Span::parse("3h4m5s"));
assert_eq!(span % Scale::Years, span);
assert_eq!(span % Scale::Seconds, Span::ZERO);
assert_eq!(span / Scale::Days, 9);
assert_eq!(span / Scale::Years, 0);
assert_eq!(span / Scale::Seconds, span.seconds);
assert_eq!(Span::DAY * 10, Span::parse("10d"));
assert_eq!(Span::WEEK * 5, Span::parse("35d"));
assert_eq!(Span::MINUTE * 90, Span::parse("1h30m"));
let span_2 = Span::WEEK
+ Span::DAY * 2
+ Span::HOUR * 3
+ Span::MINUTE * 4
+ Span::SECOND * 5;
assert_eq!(span, span_2);
assert_eq!(Span::DAY - Span::SECOND, Span::parse("23h59m59s"));
use crate::calendar::Time;
assert_eq!(
Span::DAY - Span::SECOND,
Time::hms_checked(23, 59, 59).as_span()
);
}