duration_str/lib.rs
1#![doc(
2 html_logo_url = "https://raw.githubusercontent.com/baoyachi/duration-str/master/duration-str.png"
3)]
4//! Parse string to `Duration` .
5//!
6//! The String value unit support for one of:["y","mon","w","d","h","m","s", "ms", "µs", "ns"]
7//!
8//! - y:Year. Support string value: ["y" | "year" | "Y" | "YEAR" | "Year"]. e.g. 1y
9//!
10//! - mon:Month.Support string value: ["mon" | "MON" | "Month" | "month" | "MONTH"]. e.g. 1mon
11//!
12//! - w:Week.Support string value: ["w" | "W" | "Week" | "WEEK" | "week"]. e.g. 1w
13//!
14//! - d:Day.Support string value: ["d" | "D" | "Day" | "DAY" | "day"]. e.g. 1d
15//!
16//! - h:Hour.Support string value: ["h" | "H" | "hr" | "Hour" | "HOUR" | "hour"]. e.g. 1h
17//!
18//! - m:Minute.Support string value: ["m" | "M" | "Minute" | "MINUTE" | "minute" | "min" | "MIN"]. e.g. 1m
19//!
20//! - s:Second.Support string value: ["s" | "S" | "Second" | "SECOND" | "second" | "sec" | "SEC"]. e.g. 1s
21//!
22//! - ms:Millisecond.Support string value: ["ms" | "MS" | "Millisecond" | "MilliSecond" | "MILLISECOND" | "millisecond" | "mSEC" ]. e.g. 1ms
23//!
24//! - µs:Microsecond.Support string value: ["µs" | "µS" | "µsecond" | "us" | "uS" | "usecond" | "Microsecond" | "MicroSecond" | "MICROSECOND" | "microsecond" | "µSEC"]. e.g. 1µs
25//!
26//! - ns:Nanosecond.Support string value: ["ns" | "NS" | "Nanosecond" | "NanoSecond" | "NANOSECOND" | "nanosecond" | "nSEC"]. e.g. 1ns
27//!
28//! Also, `duration_str` support time duration simple evaluation(+,*). See examples below.
29//!
30//! # Example
31//! ```rust
32//! use duration_str::parse;
33//! use std::time::Duration;
34//!
35//! let duration = parse("1d").unwrap();
36//! assert_eq!(duration, Duration::new(24 * 60 * 60, 0));
37//!
38//! let duration = parse("3m+31").unwrap(); //the default duration unit is second.
39//! assert_eq!(duration, Duration::new(211, 0));
40//!
41//! let duration = parse("3m + 31").unwrap(); //the default duration unit is second.
42//! assert_eq!(duration, Duration::new(211, 0));
43//!
44//! let duration = parse("3m + 13s + 29ms").unwrap();
45//! assert_eq!(duration, Duration::new(193, 29 * 1000 * 1000 + 0 + 0));
46//!
47//! let duration = parse("3m + 1s + 29ms +17µs").unwrap();
48//! assert_eq!(
49//! duration,
50//! Duration::new(181, 29 * 1000 * 1000 + 17 * 1000 + 0)
51//! );
52//!
53//! let duration = parse("3m 1s 29ms 17µs").unwrap();
54//! assert_eq!(
55//! duration,
56//! Duration::new(181, 29 * 1000 * 1000 + 17 * 1000 + 0)
57//! );
58//!
59//! let duration = parse("3m1s29ms17us").unwrap();
60//! assert_eq!(
61//! duration,
62//! Duration::new(181, 29 * 1000 * 1000 + 17 * 1000 + 0)
63//! );
64//!
65//! let duration = parse("1m*10").unwrap(); //the default duration unit is second.
66//! assert_eq!(duration, Duration::new(600, 0));
67//!
68//! let duration = parse("1m*10ms").unwrap();
69//! assert_eq!(duration, Duration::new(0, 600 * 1000 * 1000));
70//!
71//! let duration = parse("1m * 1ns").unwrap();
72//! assert_eq!(duration, Duration::new(0, 60));
73//!
74//! let duration = parse("1m * 1m").unwrap();
75//! assert_eq!(duration, Duration::new(3600, 0));
76//! let duration = parse("42µs").unwrap();
77//! assert_eq!(duration,Duration::from_micros(42));
78//! ```
79//!
80//! # deserialize to std::time::Duration
81//!
82#![cfg_attr(not(feature = "serde"), doc = "This requires the `serde` feature")]
83//!
84#![cfg_attr(not(feature = "serde"), doc = "```ignore")]
85#![cfg_attr(feature = "serde", doc = "```rust")]
86//! use duration_str::deserialize_duration;
87//! use serde::*;
88//! use std::time::Duration;
89//!
90//! /// Uses `deserialize_duration`.
91//! #[derive(Debug, Deserialize)]
92//! struct Config {
93//! #[serde(deserialize_with = "deserialize_duration")]
94//! time_ticker: Duration,
95//! }
96//!
97//! fn needless_main() {
98//! let json = r#"{"time_ticker":"1m+30"}"#;
99//! let config: Config = serde_json::from_str(json).unwrap();
100//! assert_eq!(config.time_ticker, Duration::new(60 + 30, 0));
101//!
102//! let json = r#"{"time_ticker":"1m+30s"}"#;
103//! let config: Config = serde_json::from_str(json).unwrap();
104//! assert_eq!(config.time_ticker, Duration::new(60 + 30, 0));
105//!
106//! let json = r#"{"time_ticker":"3m 1s 29ms 17µs"}"#;
107//! let config: Config = serde_json::from_str(json).unwrap();
108//! assert_eq!(
109//! config.time_ticker,
110//! Duration::new(181, 29 * 1000 * 1000 + 17 * 1000 + 0)
111//! );
112//!
113//! let json = r#"{"time_ticker":"3m1s29ms17us"}"#;
114//! let config: Config = serde_json::from_str(json).unwrap();
115//! assert_eq!(
116//! config.time_ticker,
117//! Duration::new(181, 29 * 1000 * 1000 + 17 * 1000 + 0)
118//! );
119//! }
120//! ```
121//!
122//! ## deserialize option duration (using the same function)
123//!
124#![cfg_attr(not(feature = "serde"), doc = "This requires the `serde` feature")]
125//!
126#![cfg_attr(not(feature = "serde"), doc = "```ignore")]
127#![cfg_attr(feature = "serde", doc = "```rust")]
128//! use duration_str::deserialize_duration;
129//! use serde::*;
130//! use std::time::Duration;
131//!
132//! /// Uses `deserialize_duration` for both required and optional fields.
133//! #[derive(Debug, Deserialize, PartialEq)]
134//! struct Config {
135//! #[serde(default, deserialize_with = "deserialize_duration")]
136//! time_ticker: Option<Duration>,
137//! name: String,
138//! }
139//!
140//! fn needless_main() {
141//! // With valid duration string
142//! let json = r#"{"time_ticker":"1m+30","name":"foo"}"#;
143//! let config: Config = serde_json::from_str(json).unwrap();
144//! assert_eq!(config.time_ticker, Some(Duration::new(90, 0)));
145//!
146//! // With null value
147//! let json = r#"{"time_ticker":null,"name":"foo"}"#;
148//! let config: Config = serde_json::from_str(json).unwrap();
149//! assert_eq!(config.time_ticker, None);
150//!
151//! // With missing field
152//! let json = r#"{"name":"foo"}"#;
153//! let config: Config = serde_json::from_str(json).unwrap();
154//! assert_eq!(config.time_ticker, None);
155//!
156//! // With empty string
157//! let json = r#"{"time_ticker":"","name":"foo"}"#;
158//! let config: Config = serde_json::from_str(json).unwrap();
159//! assert_eq!(config.time_ticker, None);
160//! }
161//! ```
162//!
163//! # deserialize to chrono::Duration
164#![cfg_attr(
165 not(all(feature = "chrono", feature = "serde")),
166 doc = "This requires both the `chrono` and `serde` features"
167)]
168//!
169#![cfg_attr(not(all(feature = "chrono", feature = "serde")), doc = "```ignore")]
170#![cfg_attr(all(feature = "chrono", feature = "serde"), doc = "```rust")]
171//! use chrono::Duration;
172//! use duration_str::deserialize_duration_chrono;
173//! use serde::*;
174//!
175//! #[derive(Debug, Deserialize)]
176//! struct Config {
177//! #[serde(deserialize_with = "deserialize_duration_chrono")]
178//! time_ticker: Duration,
179//! }
180//!
181//! fn needless_main() {
182//! let json = r#"{"time_ticker":"1m+30"}"#;
183//! let config: Config = serde_json::from_str(json).unwrap();
184//! assert_eq!(config.time_ticker, Duration::seconds(60 + 30));
185//!
186//! let json = r#"{"time_ticker":"1m+30s"}"#;
187//! let config: Config = serde_json::from_str(json).unwrap();
188//! assert_eq!(config.time_ticker, Duration::seconds(60 + 30));
189//!
190//! let json = r#"{"time_ticker":"3m 1s 29ms 17µs"}"#;
191//! let config: Config = serde_json::from_str(json).unwrap();
192//! assert_eq!(
193//! config.time_ticker,
194//! Duration::minutes(3)
195//! + Duration::seconds(1)
196//! + Duration::milliseconds(29)
197//! + Duration::microseconds(17)
198//! );
199//!
200//! let json = r#"{"time_ticker":"3m1s29ms17us"}"#;
201//! let config: Config = serde_json::from_str(json).unwrap();
202//! assert_eq!(
203//! config.time_ticker,
204//! Duration::minutes(3)
205//! + Duration::seconds(1)
206//! + Duration::milliseconds(29)
207//! + Duration::microseconds(17)
208//! );
209//! }
210//! ```
211
212mod error;
213pub(crate) mod ext;
214pub(crate) mod macros;
215mod parser;
216#[cfg(feature = "serde")]
217mod serde;
218mod unit;
219
220pub use parser::parse;
221#[cfg(feature = "serde")]
222pub use serde::*;
223use std::fmt::{Debug, Display};
224
225use rust_decimal::prelude::ToPrimitive;
226use rust_decimal::Decimal;
227use std::str::FromStr;
228use std::time::Duration;
229
230pub use crate::error::DError;
231use crate::unit::TimeUnit;
232#[cfg(feature = "chrono")]
233pub use naive_date::{
234 after_naive_date, after_naive_date_time, before_naive_date, before_naive_date_time,
235};
236
237pub use ext::*;
238
239pub type DResult<T> = Result<T, DError>;
240
241const ONE_MICROSECOND_NANOSECOND: u64 = 1000;
242const ONE_MILLISECOND_NANOSECOND: u64 = 1000 * ONE_MICROSECOND_NANOSECOND;
243const ONE_SECOND_NANOSECOND: u64 = 1000 * ONE_MILLISECOND_NANOSECOND;
244const ONE_MINUTE_NANOSECOND: u64 = 60 * ONE_SECOND_NANOSECOND;
245const ONE_HOUR_NANOSECOND: u64 = 60 * ONE_MINUTE_NANOSECOND;
246const ONE_DAY_NANOSECOND: u64 = 24 * ONE_HOUR_NANOSECOND;
247const ONE_WEEK_NANOSECOND: u64 = 7 * ONE_DAY_NANOSECOND;
248const ONE_MONTH_NANOSECOND: u64 = 30 * ONE_DAY_NANOSECOND;
249const ONE_YEAR_NANOSECOND: u64 = 365 * ONE_DAY_NANOSECOND;
250
251// const ONE_SECOND_DECIMAL: Decimal = 1_000_000_000.into();
252fn one_second_decimal() -> Decimal {
253 1_000_000_000.into()
254}
255
256const PLUS: &str = "+";
257const STAR: &str = "*";
258
259trait ExpectErr {
260 type Output: Debug;
261
262 fn expect_val() -> Self::Output;
263
264 fn get_expect_val() -> &'static str;
265 fn expect_err<S: AsRef<str> + Display>(s: S) -> String;
266}
267
268#[macro_export]
269macro_rules! impl_expect_err {
270 ($type:ty, $output:ty, [$($val:tt),* $(,)?]) => {
271 impl ExpectErr for $type {
272 type Output = $output;
273
274 fn expect_val() -> Self::Output {
275 [$($val),*]
276 }
277
278 fn expect_err<S: AsRef<str> + Display>(s: S) -> String {
279 format!("expect one of:{:?}, but find:{}", Self::expect_val(), s)
280 }
281
282 fn get_expect_val() -> &'static str {
283 static EXPECT_VAL_STR: &str = concat!(
284 "[",
285 impl_expect_err_internal!($($val),*),
286 "]"
287 );
288 EXPECT_VAL_STR
289 }
290 }
291 };
292}
293
294#[macro_export]
295macro_rules! impl_expect_err_internal {
296 // match empty
297 () => {
298 ""
299 };
300 // match single type
301 ($first:expr) => {
302 stringify!($first)
303 };
304 // match multi type
305 ($first:expr, $($rest:expr),*) => {
306 concat!(
307 stringify!($first),
308 ", ",
309 impl_expect_err_internal!($($rest),*)
310 )
311 };
312}
313
314#[derive(Debug, Eq, PartialEq, Clone)]
315enum CondUnit {
316 Plus,
317 Star,
318}
319
320impl FromStr for CondUnit {
321 type Err = String;
322
323 fn from_str(s: &str) -> Result<Self, Self::Err> {
324 match s {
325 "+" => Ok(CondUnit::Plus),
326 "*" => Ok(CondUnit::Star),
327 _ => Err(Self::expect_err(s)),
328 }
329 }
330}
331
332impl_expect_err!(CondUnit, [char; 2], ['+', '*']);
333
334impl CondUnit {
335 fn init() -> (Self, u64) {
336 (CondUnit::Star, ONE_SECOND_NANOSECOND)
337 }
338
339 fn contain(c: char) -> bool {
340 Self::expect_val().contains(&c)
341 }
342
343 fn change_duration(&self) -> u64 {
344 match self {
345 CondUnit::Plus => 0,
346 CondUnit::Star => ONE_SECOND_NANOSECOND,
347 }
348 }
349
350 fn calc(&self, x: u64, y: u64) -> DResult<Duration> {
351 let nano_second = match self {
352 CondUnit::Plus => x.checked_add(y).ok_or(DError::OverflowError)?,
353 CondUnit::Star => {
354 let x: Decimal = x.into();
355 let y: Decimal = y.into();
356 let ret = (x / one_second_decimal())
357 .checked_mul(y / one_second_decimal())
358 .ok_or(DError::OverflowError)?
359 .checked_mul(one_second_decimal())
360 .ok_or(DError::OverflowError)?;
361 ret.to_u64().ok_or(DError::OverflowError)?
362 }
363 };
364 Ok(Duration::from_nanos(nano_second))
365 }
366}
367
368trait Calc<T> {
369 fn calc(&self) -> DResult<T>;
370}
371
372impl Calc<(CondUnit, u64)> for Vec<(&str, CondUnit, TimeUnit)> {
373 fn calc(&self) -> DResult<(CondUnit, u64)> {
374 let (mut init_cond, mut init_duration) = CondUnit::init();
375 for (index, (val, cond, time_unit)) in self.iter().enumerate() {
376 if index == 0 {
377 init_cond = cond.clone();
378 init_duration = init_cond.change_duration();
379 } else if &init_cond != cond {
380 return Err(DError::ParseError(format!(
381 "not support '{}' with '{}' calculate",
382 init_cond, cond
383 )));
384 }
385 match init_cond {
386 CondUnit::Plus => {
387 init_duration = init_duration
388 .checked_add(time_unit.duration(val)?)
389 .ok_or(DError::OverflowError)?;
390 }
391 CondUnit::Star => {
392 let time: Decimal = time_unit.duration(val)?.into();
393 let i = time / one_second_decimal();
394 let mut init: Decimal = init_duration.into();
395 init = init.checked_mul(i).ok_or(DError::OverflowError)?;
396 init_duration = init.to_u64().ok_or(DError::OverflowError)?;
397 }
398 }
399 }
400 Ok((init_cond, init_duration))
401 }
402}
403
404impl Display for CondUnit {
405 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
406 let str = match self {
407 Self::Plus => PLUS.to_string(),
408 Self::Star => STAR.to_string(),
409 };
410 write!(f, "{}", str)
411 }
412}
413
414/// convert `Into<String>` to `std::time::Duration`
415///
416/// # Example
417///
418/// ```rust
419/// use duration_str::parse;
420/// use std::time::Duration;
421///
422/// // supports units
423/// let duration = parse("1d").unwrap();
424/// assert_eq!(duration,Duration::new(24*60*60,0));
425///
426/// // supports addition
427/// let duration = parse("3m+31").unwrap();
428/// assert_eq!(duration,Duration::new(211,0));
429///
430/// // spaces are optional
431/// let duration = parse("3m + 31").unwrap();
432/// assert_eq!(duration,Duration::new(211,0));
433///
434/// // plus sign is optional
435/// let duration = parse("3m 31").unwrap();
436/// assert_eq!(duration,Duration::new(211,0));
437///
438/// // both plus and spaces are optional
439/// let duration = parse("3m31").unwrap();
440/// assert_eq!(duration,Duration::new(211,0));
441///
442/// // supports multiplication
443/// let duration = parse("1m*10").unwrap();
444/// assert_eq!(duration,Duration::new(600,0));
445///
446/// // spaces are optional
447/// let duration = parse("1m * 10").unwrap();
448/// assert_eq!(duration,Duration::new(600,0));
449/// ```
450pub fn parse_std(input: impl AsRef<str>) -> Result<Duration, String> {
451 parse(input.as_ref())
452}
453
454/// convert `Into<String>` to `chrono::Duration`
455///
456/// # Example
457///
458/// ```rust
459/// use duration_str::parse_chrono;
460/// use chrono::Duration;
461///
462/// // supports units
463/// let duration = parse_chrono("1d").unwrap();
464/// assert_eq!(duration,Duration::seconds(24*60*60));
465///
466/// // supports addition
467/// let duration = parse_chrono("3m+31").unwrap();
468/// assert_eq!(duration,Duration::seconds(211));
469///
470/// // spaces are optional
471/// let duration = parse_chrono("3m + 31").unwrap();
472/// assert_eq!(duration,Duration::seconds(211));
473///
474/// // plus sign is optional
475/// let duration = parse_chrono("3m 31").unwrap();
476/// assert_eq!(duration,Duration::seconds(211));
477///
478/// // both plus and spaces are optional
479/// let duration = parse_chrono("3m31").unwrap();
480/// assert_eq!(duration,Duration::seconds(211));
481///
482/// // supports multiplication
483/// let duration = parse_chrono("1m*10").unwrap();
484/// assert_eq!(duration,Duration::seconds(600));
485///
486/// // spaces are optional
487/// let duration = parse_chrono("1m * 10").unwrap();
488/// assert_eq!(duration,Duration::seconds(600));
489/// ```
490#[cfg(feature = "chrono")]
491pub fn parse_chrono(input: impl AsRef<str>) -> Result<chrono::Duration, String> {
492 let std_duration = parse_std(input)?;
493 let duration = chrono::Duration::from_std(std_duration).map_err(|e| e.to_string())?;
494 Ok(duration)
495}
496
497/// convert `Into<String>` to `time::Duration`
498///
499/// # Example
500///
501/// ```rust
502/// use duration_str::parse_time;
503/// use time::Duration;
504///
505/// // supports units
506/// let duration = parse_time("1d").unwrap();
507/// assert_eq!(duration,Duration::seconds(24*60*60));
508///
509/// // supports addition
510/// let duration = parse_time("3m+31").unwrap();
511/// assert_eq!(duration,Duration::seconds(211));
512///
513/// // spaces are optional
514/// let duration = parse_time("3m + 31").unwrap();
515/// assert_eq!(duration,Duration::seconds(211));
516///
517/// // plus sign is optional
518/// let duration = parse_time("3m 31").unwrap();
519/// assert_eq!(duration,Duration::seconds(211));
520///
521/// // both plus and spaces are optional
522/// let duration = parse_time("3m31").unwrap();
523/// assert_eq!(duration,Duration::seconds(211));
524///
525/// // supports multiplication
526/// let duration = parse_time("1m*10").unwrap();
527/// assert_eq!(duration,Duration::seconds(600));
528///
529/// // spaces are optional
530/// let duration = parse_time("1m * 10").unwrap();
531/// assert_eq!(duration,Duration::seconds(600));
532/// ```
533#[cfg(feature = "time")]
534pub fn parse_time(input: impl AsRef<str>) -> Result<time::Duration, String> {
535 let std_duration = parse_std(input)?;
536 let duration = time::Duration::try_from(std_duration).map_err(|e| e.to_string())?;
537 Ok(duration)
538}
539
540#[cfg(feature = "chrono")]
541mod naive_date {
542 use crate::parse_chrono;
543 use chrono::Utc;
544
545 #[allow(dead_code)]
546 pub enum TimeHistory {
547 Before,
548 After,
549 }
550
551 #[cfg(feature = "chrono")]
552 pub fn calc_naive_date_time(
553 input: impl AsRef<str>,
554 history: TimeHistory,
555 ) -> Result<chrono::NaiveDateTime, String> {
556 let duration = parse_chrono(input)?;
557 let time = match history {
558 TimeHistory::Before => (Utc::now() - duration).naive_utc(),
559 TimeHistory::After => (Utc::now() + duration).naive_utc(),
560 };
561 Ok(time)
562 }
563
564 macro_rules! gen_naive_date_func {
565 ($date_time:ident,$date:ident,$history:expr) => {
566 #[allow(dead_code)]
567 #[cfg(feature = "chrono")]
568 pub fn $date_time(input: impl AsRef<str>) -> Result<chrono::NaiveDateTime, String> {
569 calc_naive_date_time(input, $history)
570 }
571
572 #[allow(dead_code)]
573 #[cfg(feature = "chrono")]
574 pub fn $date(input: impl AsRef<str>) -> Result<chrono::NaiveDate, String> {
575 let date: chrono::NaiveDateTime = calc_naive_date_time(input, $history)?;
576 Ok(date.date())
577 }
578 };
579 }
580
581 gen_naive_date_func!(
582 before_naive_date_time,
583 before_naive_date,
584 TimeHistory::Before
585 );
586
587 gen_naive_date_func!(after_naive_date_time, after_naive_date, TimeHistory::After);
588}