#![doc(issue_tracker_base_url = "https://github.com/Mnwa/ms/issues/")]
#![doc(html_root_url = "https://docs.rs/ms-converter/")]
#![no_std]
extern crate std;
use std::borrow::Cow;
use std::fmt::Formatter;
use std::format;
use std::ops::{Add, Mul, Sub};
use std::string::String;
use std::time::Duration;
pub const SECOND: f64 = 1000_f64;
pub const MINUTE: f64 = SECOND * 60_f64;
pub const HOUR: f64 = MINUTE * 60_f64;
pub const DAY: f64 = HOUR * 24_f64;
pub const WEEK: f64 = DAY * 7_f64;
pub const YEAR: f64 = DAY * 365.25_f64;
#[inline(always)]
pub fn ms<'a, T>(s: T) -> Result<i64, Error>
where
T: Into<Cow<'a, str>>,
{
let s = &*s.into();
let (value, postfix): (&str, &str) = s
.find(|c: char| !matches!(c, '0'..='9' | '.' | '-' | '+'))
.map_or((s, ""), |vi| s.split_at(vi));
let postfix = get_byte_postfix(postfix);
parse(value.as_bytes())
.and_then(move |value| Ok(get_modification(postfix)? * value))
.map(|v| v.round() as i64)
}
#[inline]
pub fn get_duration_by_postfix<'a, P>(milliseconds: i64, postfix: P) -> Result<String, Error>
where
P: Into<Cow<'a, str>>,
{
let postfix = &*postfix.into();
let b_postfix = get_byte_postfix(postfix);
let v = get_modification(b_postfix)?;
Ok(format!(
"{}{}",
(milliseconds as f64 / v).round() as i64,
postfix
))
}
#[inline]
pub fn get_max_possible_duration(milliseconds: i64) -> Result<String, Error> {
let postfix = match milliseconds.abs() {
m if m >= DAY as i64 => "d",
m if m >= HOUR as i64 => "h",
m if m >= MINUTE as i64 => "m",
m if m >= SECOND as i64 => "s",
_ => "ms",
};
get_duration_by_postfix(milliseconds, postfix)
}
#[inline]
pub fn get_max_possible_duration_long(milliseconds: i64) -> Result<String, Error> {
let postfix = match milliseconds.abs() {
m if m >= DAY as i64 => check_postfix(m, DAY, " day", " days"),
m if m >= HOUR as i64 => check_postfix(m, HOUR, " hour", " hours"),
m if m >= MINUTE as i64 => check_postfix(m, MINUTE, " minute", " minutes"),
m if m >= SECOND as i64 => check_postfix(m, SECOND, " second", " seconds"),
m => check_postfix(m, 1f64, " millisecond", " milliseconds"),
};
get_duration_by_postfix(milliseconds, postfix)
}
#[inline(always)]
#[doc(hidden)]
fn check_postfix<'a>(
milliseconds: i64,
period: f64,
postfix: &'a str,
postfix_mul: &'a str,
) -> &'a str {
if milliseconds as f64 >= 1.5 * period {
return postfix_mul;
}
postfix
}
#[inline(always)]
#[doc(hidden)]
fn get_byte_postfix(postfix: &str) -> &[u8] {
let b_postfix = postfix.as_bytes();
match b_postfix.first() {
Some(c) if c.is_ascii_whitespace() => &b_postfix[1..],
_ => b_postfix,
}
}
#[inline(always)]
#[doc(hidden)]
fn get_modification(postfix: &[u8]) -> Result<f64, Error> {
match postfix.first() {
Some(b'y') if matches!(postfix, b"years" | b"year" | b"yrs" | b"yr" | b"y") => Ok(YEAR),
Some(b'w') if matches!(postfix, b"weeks" | b"week" | b"w") => Ok(WEEK),
Some(b'd') if matches!(postfix, b"days" | b"day" | b"d") => Ok(DAY),
Some(b'h') if matches!(postfix, b"hours" | b"hour" | b"hrs" | b"hr" | b"h") => Ok(HOUR),
Some(b'm') if matches!(postfix, b"minutes" | b"minute" | b"mins" | b"min" | b"m") => {
Ok(MINUTE)
}
None | Some(b'm')
if matches!(
postfix,
b"milliseconds" | b"millisecond" | b"msecs" | b"msec" | b"ms" | b""
) =>
{
Ok(1f64)
}
Some(b's') if matches!(postfix, b"seconds" | b"second" | b"secs" | b"sec" | b"s") => {
Ok(SECOND)
}
_ => Err(Error::new("invalid postfix")),
}
}
#[inline(always)]
#[doc(hidden)]
fn parse(mut num: &[u8]) -> Result<f64, Error> {
let sign = match num.first() {
Some(b'-') => {
num = &num[1..];
-1_f64
}
Some(b'+') => {
num = &num[1..];
1_f64
}
_ => 1_f64,
};
let (mut ind, mut dist) = num
.iter()
.take_while(|b| b.is_ascii_digit())
.map(|b| b.sub(b'0') as f64)
.fold((0, 0_f64), |(ind, dist), b| {
(ind.add(1), dist.mul_add(10_f64, b))
});
if matches!(num.get(ind), Some(b'.')) {
ind = ind.add(1)
}
if ind < num.len() {
let (pow, temp) = num[ind..]
.iter()
.take_while(|b| b.is_ascii_digit())
.map(|b| b.sub(b'0') as f64)
.fold((1, 0_f64), |(pow, temp), b| {
(pow.add(1), temp.mul_add(10_f64, b))
});
ind = ind.add(pow as usize).sub(1);
let pow = pow.mul(-1_i32).add(1);
dist = dist.add(temp.mul((10_f64).powi(pow)));
}
if num.len() == ind {
Ok(dist.copysign(sign))
} else {
Err(Error::new("invalid value"))
}
}
#[macro_export]
macro_rules! ms_expr {
($type:ty, $x:literal $(milliseconds)?$(millisecond)?$(msecs)?$(msec)?$(ms)?) => {{
let x: $type = $x;
x
}};
($type:ty, $x:literal $(seconds)?$(second)?$(secs)?$(sec)?$(s)?) => {{
let x: $type = $x * ($crate::SECOND as $type);
x
}};
($type:ty, $x:literal $(minutes)?$(minute)?$(mins)?$(min)?$(m)?) => {{
let x: $type = $x * ($crate::MINUTE as $type);
x
}};
($type:ty, $x:literal $(hours)?$(hour)?$(hrs)?$(hr)?$(h)?) => {{
let x: $type = $x * ($crate::HOUR as $type);
x
}};
($type:ty, $x:literal $(days)?$(day)?$(d)?) => {{
let x: $type = $x * ($crate::DAY as $type);
x
}};
($type:ty, $x:literal $(weeks)?$(week)?$(w)?) => {{
let x: $type = $x * ($crate::WEEK as $type);
x
}};
($type:ty, $x:literal $(years)?$(year)?$(yrs)?$(yr)?$(y)?) => {{
let x: $type = $x * ($crate::YEAR as $type);
x
}};
}
pub fn ms_into_time<'a, T>(s: T) -> Result<Duration, Error>
where
T: Into<Cow<'a, str>>,
{
let milliseconds = ms(s)?;
if milliseconds < 0 {
return Err(Error::new("time.Duration cannot work with negative values"));
}
Ok(Duration::from_millis(milliseconds as u64))
}
#[derive(Debug)]
pub struct Error {
message: &'static str,
}
impl Error {
pub fn new(message: &'static str) -> Error {
Error { message }
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for Error {}
#[cfg(test)]
mod tests;