Skip to content

Commit baaa0c0

Browse files
authored
Merge pull request #67 from baoyachi/performance_optimization
Independent calc feature
2 parents d6068e9 + fc3cc5c commit baaa0c0

File tree

6 files changed

+74
-20
lines changed

6 files changed

+74
-20
lines changed

.github/workflows/check.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,15 @@ jobs:
7777
- name: Run tests
7878
run: cargo test --all-features --all
7979
- name: Run tests without default features.
80-
run: cargo test --no-default-features
80+
run: cargo test --no-default-features --features="calc"
8181
- name: Run examples with deserialize_duration
8282
run: cargo run --example deserialize_duration
8383
- name: Run examples with deserialize_duration_chrono
8484
run: cargo run --example deserialize_duration_chrono
8585
- name: Run examples with deserialize_duration_time
8686
run: cargo run --example deserialize_duration_time
87+
- name: Run examples with no_calc
88+
run: cargo run --example no_calc --features="no_calc"
8789
- name: Coverage
8890
if: matrix.rust == 'stable'
8991
run: cargo tarpaulin -o Lcov --output-dir ./coverage

Cargo.toml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "duration-str"
3-
version = "0.15.1"
3+
version = "0.16.0"
44
authors = ["baoyachi <[email protected]>"]
55
edition = "2021"
66
description = "duration string parser"
@@ -12,16 +12,18 @@ license = "Apache-2.0"
1212
exclude = ["duration-str.png", "./playground"]
1313

1414
[features]
15-
default = ["chrono", "serde", "time"]
15+
default = ["chrono", "serde", "time", "calc"]
1616

1717
lowercase = []
18+
no_calc = []
19+
calc = []
1820

1921
[dependencies]
2022
thiserror = "2.0.0"
2123
chrono = { version = "0.4.38", optional = true, default-features = false, features = ["now"] }
2224
time = { version = "0.3.17", optional = true, default-features = false }
2325

24-
serde = { version = "1.0.147", features = ["derive"], optional = true}
26+
serde = { version = "1.0.147", features = ["derive"], optional = true }
2527
rust_decimal = { version = "1.29.1", default-features = false }
2628
winnow = "0.7.3"
2729

@@ -32,7 +34,7 @@ criterion = "0.5"
3234
[[bench]]
3335
name = "parser_benchmark"
3436
harness = false
35-
required-features = ["lowercase"]
37+
required-features = ["lowercase", "no_calc"]
3638

3739
# docs.rs-specific configuration
3840
[package.metadata.docs.rs]
@@ -50,3 +52,8 @@ required-features = ["chrono", "serde"]
5052
[[example]]
5153
name = "deserialize_duration_time"
5254
required-features = ["time", "serde"]
55+
56+
[[example]]
57+
name = "no_calc"
58+
required-features = ["no_calc"]
59+

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
Suggestion: It is recommended to enable the `lowercase` feature to improve performance:
3434
* Default: Calls `to_lowercase()` each time, designed for compatibility, suitable for flexible input but with lower performance.
3535
* `lowercase` feature: Skips the conversion, ideal for lowercase input scenarios, offering better performance."
36+
* `no_calc` feature: When enabled, the parse function only parses and sums expression values without complex logic, ideal for high-performance scenarios not requiring intricate calculations.
37+
38+
3639

3740
## Notice ⚠️
3841

benches/parser_benchmark.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// use: cargo bench --features lowercase
1+
// use: cargo bench --features "lowercase no_calc"
22

33
use criterion::{criterion_group, criterion_main, Criterion};
44
use std::time::Duration;

examples/no_calc.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use duration_str::parse_std;
2+
use std::time::Duration;
3+
4+
fn main() {
5+
let duration = parse_std("2h 37m").unwrap();
6+
assert_eq!(duration, Duration::new(9420, 0));
7+
8+
let duration = parse_std("2h 37m ").unwrap();
9+
assert_eq!(duration, Duration::new(9420, 0));
10+
11+
let duration = parse_std(" 2h 37m").unwrap();
12+
assert_eq!(duration, Duration::new(9420, 0));
13+
14+
let duration = parse_std(" 2h 37m ").unwrap();
15+
assert_eq!(duration, Duration::new(9420, 0));
16+
17+
assert!(parse_std("").is_err());
18+
}

src/parser.rs

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use crate::unit::{opt_unit_abbr, TimeUnit};
22
use crate::{Calc, CondUnit, ExpectErr};
33
use std::time::Duration;
44
use winnow::ascii::{digit1, multispace0};
5-
use winnow::combinator::trace;
65
use winnow::combinator::{alt, cut_err};
76
use winnow::combinator::{eof, peek, repeat};
87
use winnow::error::{ContextError, StrContext, StrContextValue};
@@ -68,20 +67,43 @@ pub(crate) fn cond_time<'a>(input: &mut &'a str) -> WResult<Vec<(&'a str, CondUn
6867

6968
pub fn parse(input: impl AsRef<str>) -> Result<Duration, String> {
7069
let input = input.as_ref();
71-
let (unit_time, cond_val) = (parse_expr_time, trace("cond_time", cond_time))
72-
.parse(input)
73-
.map_err(|e| format!("{}", e))?;
74-
75-
let (init_cond, init_duration) = if cond_val.is_empty() {
76-
CondUnit::init()
77-
} else {
78-
cond_val.calc().map_err(|err| err.to_string())?
79-
};
70+
if input.is_empty() {
71+
return Err(String::from("Empty input"));
72+
}
73+
#[cfg(all(feature = "no_calc", not(feature = "calc")))]
74+
{
75+
use crate::DError;
76+
77+
let d = repeat(0.., parse_expr_time)
78+
.try_fold(
79+
Default::default,
80+
|mut acc: u64, item| -> Result<_, DError> {
81+
acc = acc.checked_add(item).ok_or(DError::OverflowError)?;
82+
Ok(acc)
83+
},
84+
)
85+
.parse(input)
86+
.map_err(|err| err.to_string())?;
87+
return Ok(Duration::from_nanos(d));
88+
}
8089

81-
let duration = init_cond
82-
.calc(unit_time, init_duration)
83-
.map_err(|err| err.to_string())?;
84-
Ok(duration)
90+
#[cfg(feature = "calc")]
91+
{
92+
let (unit_time, cond_val) = (parse_expr_time, cond_time)
93+
.parse(input)
94+
.map_err(|e| format!("{}", e))?;
95+
96+
let (init_cond, init_duration) = if cond_val.is_empty() {
97+
CondUnit::init()
98+
} else {
99+
cond_val.calc().map_err(|err| err.to_string())?
100+
};
101+
102+
let duration = init_cond
103+
.calc(unit_time, init_duration)
104+
.map_err(|err| err.to_string())?;
105+
Ok(duration)
106+
}
85107
}
86108

87109
#[cfg(test)]
@@ -197,6 +219,8 @@ expected ["y", "mon", "w", "d", "h", "m", "s", "ms", "µs", "us", "ns"]"#
197219
expected ['+', '*']"#
198220
.trim()
199221
);
222+
223+
assert_eq!(catch_err!(parse("")), "Empty input");
200224
}
201225

202226
#[test]

0 commit comments

Comments
 (0)