#![doc(
html_logo_url = "https://raw.githubusercontent.com/baoyachi/duration-str/master/duration-str.png"
)]
#![cfg_attr(not(feature = "serde"), doc = "This requires the `serde` feature")]
#![cfg_attr(not(feature = "serde"), doc = "```ignore")]
#![cfg_attr(feature = "serde", doc = "```rust")]
#![cfg_attr(not(feature = "serde"), doc = "This requires the `serde` feature")]
#![cfg_attr(not(feature = "serde"), doc = "```ignore")]
#![cfg_attr(feature = "serde", doc = "```rust")]
#![cfg_attr(
not(all(feature = "chrono", feature = "serde")),
doc = "This requires both the `chrono` and `serde` features"
)]
#![cfg_attr(not(all(feature = "chrono", feature = "serde")), doc = "```ignore")]
#![cfg_attr(all(feature = "chrono", feature = "serde"), doc = "```rust")]
mod error;
pub(crate) mod ext;
pub(crate) mod macros;
mod parser;
#[cfg(feature = "serde")]
mod serde;
mod unit;
pub use parser::parse;
#[cfg(feature = "serde")]
pub use serde::*;
use std::fmt::{Debug, Display};
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use std::str::FromStr;
use std::time::Duration;
pub use crate::error::DError;
use crate::unit::TimeUnit;
#[cfg(feature = "chrono")]
pub use naive_date::{
after_naive_date, after_naive_date_time, before_naive_date, before_naive_date_time,
};
pub use ext::*;
pub type DResult<T> = Result<T, DError>;
const ONE_MICROSECOND_NANOSECOND: u64 = 1000;
const ONE_MILLISECOND_NANOSECOND: u64 = 1000 * ONE_MICROSECOND_NANOSECOND;
const ONE_SECOND_NANOSECOND: u64 = 1000 * ONE_MILLISECOND_NANOSECOND;
const ONE_MINUTE_NANOSECOND: u64 = 60 * ONE_SECOND_NANOSECOND;
const ONE_HOUR_NANOSECOND: u64 = 60 * ONE_MINUTE_NANOSECOND;
const ONE_DAY_NANOSECOND: u64 = 24 * ONE_HOUR_NANOSECOND;
const ONE_WEEK_NANOSECOND: u64 = 7 * ONE_DAY_NANOSECOND;
const ONE_MONTH_NANOSECOND: u64 = 30 * ONE_DAY_NANOSECOND;
const ONE_YEAR_NANOSECOND: u64 = 365 * ONE_DAY_NANOSECOND;
fn one_second_decimal() -> Decimal {
1_000_000_000.into()
}
const PLUS: &str = "+";
const STAR: &str = "*";
trait ExpectErr {
type Output: Debug;
fn expect_val() -> Self::Output;
fn get_expect_val() -> &'static str;
fn expect_err<S: AsRef<str> + Display>(s: S) -> String;
}
#[macro_export]
macro_rules! impl_expect_err {
($type:ty, $output:ty, [$($val:tt),* $(,)?]) => {
impl ExpectErr for $type {
type Output = $output;
fn expect_val() -> Self::Output {
[$($val),*]
}
fn expect_err<S: AsRef<str> + Display>(s: S) -> String {
format!("expect one of:{:?}, but find:{}", Self::expect_val(), s)
}
fn get_expect_val() -> &'static str {
static EXPECT_VAL_STR: &str = concat!(
"[",
impl_expect_err_internal!($($val),*),
"]"
);
EXPECT_VAL_STR
}
}
};
}
#[macro_export]
macro_rules! impl_expect_err_internal {
() => {
""
};
($first:expr) => {
stringify!($first)
};
($first:expr, $($rest:expr),*) => {
concat!(
stringify!($first),
", ",
impl_expect_err_internal!($($rest),*)
)
};
}
#[derive(Debug, Eq, PartialEq, Clone)]
enum CondUnit {
Plus,
Star,
}
impl FromStr for CondUnit {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"+" => Ok(CondUnit::Plus),
"*" => Ok(CondUnit::Star),
_ => Err(Self::expect_err(s)),
}
}
}
impl_expect_err!(CondUnit, [char; 2], ['+', '*']);
impl CondUnit {
fn init() -> (Self, u64) {
(CondUnit::Star, ONE_SECOND_NANOSECOND)
}
fn contain(c: char) -> bool {
Self::expect_val().contains(&c)
}
fn change_duration(&self) -> u64 {
match self {
CondUnit::Plus => 0,
CondUnit::Star => ONE_SECOND_NANOSECOND,
}
}
fn calc(&self, x: u64, y: u64) -> DResult<Duration> {
let nano_second = match self {
CondUnit::Plus => x.checked_add(y).ok_or(DError::OverflowError)?,
CondUnit::Star => {
let x: Decimal = x.into();
let y: Decimal = y.into();
let ret = (x / one_second_decimal())
.checked_mul(y / one_second_decimal())
.ok_or(DError::OverflowError)?
.checked_mul(one_second_decimal())
.ok_or(DError::OverflowError)?;
ret.to_u64().ok_or(DError::OverflowError)?
}
};
Ok(Duration::from_nanos(nano_second))
}
}
trait Calc<T> {
fn calc(&self) -> DResult<T>;
}
impl Calc<(CondUnit, u64)> for Vec<(&str, CondUnit, TimeUnit)> {
fn calc(&self) -> DResult<(CondUnit, u64)> {
let (mut init_cond, mut init_duration) = CondUnit::init();
for (index, (val, cond, time_unit)) in self.iter().enumerate() {
if index == 0 {
init_cond = cond.clone();
init_duration = init_cond.change_duration();
} else if &init_cond != cond {
return Err(DError::ParseError(format!(
"not support '{}' with '{}' calculate",
init_cond, cond
)));
}
match init_cond {
CondUnit::Plus => {
init_duration = init_duration
.checked_add(time_unit.duration(val)?)
.ok_or(DError::OverflowError)?;
}
CondUnit::Star => {
let time: Decimal = time_unit.duration(val)?.into();
let i = time / one_second_decimal();
let mut init: Decimal = init_duration.into();
init = init.checked_mul(i).ok_or(DError::OverflowError)?;
init_duration = init.to_u64().ok_or(DError::OverflowError)?;
}
}
}
Ok((init_cond, init_duration))
}
}
impl Display for CondUnit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
Self::Plus => PLUS.to_string(),
Self::Star => STAR.to_string(),
};
write!(f, "{}", str)
}
}
pub fn parse_std(input: impl AsRef<str>) -> Result<Duration, String> {
parse(input.as_ref())
}
#[cfg(feature = "chrono")]
pub fn parse_chrono(input: impl AsRef<str>) -> Result<chrono::Duration, String> {
let std_duration = parse_std(input)?;
let duration = chrono::Duration::from_std(std_duration).map_err(|e| e.to_string())?;
Ok(duration)
}
#[cfg(feature = "time")]
pub fn parse_time(input: impl AsRef<str>) -> Result<time::Duration, String> {
let std_duration = parse_std(input)?;
let duration = time::Duration::try_from(std_duration).map_err(|e| e.to_string())?;
Ok(duration)
}
#[cfg(feature = "chrono")]
mod naive_date {
use crate::parse_chrono;
use chrono::Utc;
#[allow(dead_code)]
pub enum TimeHistory {
Before,
After,
}
#[cfg(feature = "chrono")]
pub fn calc_naive_date_time(
input: impl AsRef<str>,
history: TimeHistory,
) -> Result<chrono::NaiveDateTime, String> {
let duration = parse_chrono(input)?;
let time = match history {
TimeHistory::Before => (Utc::now() - duration).naive_utc(),
TimeHistory::After => (Utc::now() + duration).naive_utc(),
};
Ok(time)
}
macro_rules! gen_naive_date_func {
($date_time:ident,$date:ident,$history:expr) => {
#[allow(dead_code)]
#[cfg(feature = "chrono")]
pub fn $date_time(input: impl AsRef<str>) -> Result<chrono::NaiveDateTime, String> {
calc_naive_date_time(input, $history)
}
#[allow(dead_code)]
#[cfg(feature = "chrono")]
pub fn $date(input: impl AsRef<str>) -> Result<chrono::NaiveDate, String> {
let date: chrono::NaiveDateTime = calc_naive_date_time(input, $history)?;
Ok(date.date())
}
};
}
gen_naive_date_func!(
before_naive_date_time,
before_naive_date,
TimeHistory::Before
);
gen_naive_date_func!(after_naive_date_time, after_naive_date, TimeHistory::After);
}