Table of Contents
Overview
fundu
provides a flexible and fast parser to convert rust strings into a std::time::Duration
and for
negative durations into a time::Duration
. Some examples for valid input strings with the
standard
feature:
"1.41"
"42"
"2e-8"
,"2e+8"
(or likewise"2.0e8"
)".5"
or likewise"0.5"
"3."
or likewise"3.0"
"inf"
,"+inf"
,"infinity"
,"+infinity"
"1w"
(1 week) or likewise"7d"
,"168h"
,"10080m"
,"604800s"
, ...
For examples of the custom
feature see Customization section.
A quick summary of features provided by this crate:
- Precision: There are no floating point calculations and the input is precisely parsed as it
is. So, what you put in you is what you get out within the range of a
Duration
. (See also Comparison) - Performance: The parser is blazingly fast (Benchmarks)
- Customization:
TimeUnits
, the number format and other aspects are easily configurable (Customization) - Sound limits: The duration evaluates to
Duration::MAX
if the input number was larger than that maximum or if the input string was positiveinfinity
. - Negative_Durations: Negative numbers can be parsed to negative
time::Duration
s when thenegative
feature is activated. - Error handling: The error messages try to be informative on their own but can also be easily adjusted (See also Examples)
fundu
aims for good performance and being a lightweight crate. It is purely built on top of the
rust stdlib
, and there are no additional dependencies required in the standard configuration. The
accepted number format is per default the scientific floating point format and compatible with
f64::from_str
. However, the number format and other aspects can be customized
up to formats like systemd time
spans. See also the examples
Examples section and the examples folder. For a direct comparison of
fundu
vs the rust native methods Duration::(try_)from_secs_f64
see
Comparison.
For further details see the Documentation!
Installation
Add this to Cargo.toml
for fundu
with the standard
feature.
[]
= "0.5.1"
fundu is split into two main features, standard
(providing DurationParser
and parse_duration
)
and custom
(providing the CustomDurationParser
). The first is described here in in detail, the
latter adds fully customizable identifiers for time units. Most of the time only one
of the parsers is needed. To include only the CustomDurationParser
add the following to
Cargo.toml
:
[]
= { = "0.5.1", = false, = ["custom"] }
Activating the negative
feature allows parsing negative numbers to negative time::Duration
s.
Examples
If only the default parser is required once, then the parse_duration
method can be used.
use parse_duration;
use Duration;
let input = "1.0e2s";
assert_eq!;
When a customization of the accepted TimeUnits is required, then
DurationParser::with_time_units
can be used.
use DurationParser;
use *;
use Duration;
let input = "3m";
assert_eq!;
When no time units are configured, seconds is assumed.
use DurationParser;
use Duration;
let input = "1.0e2";
assert_eq!;
However, setting the default time unit to something different than seconds can be achieved with
use ;
use Duration;
assert_eq!;
Note the following will return an error because y
(Years) is not in the default set of
TimeUnits.
use DurationParser;
assert_eq!;
The parser is reusable and the set of time units is fully customizable
use ;
use Duration;
let parser = with_time_units;
for in &
The identifiers for time units can be fully customized with any number of valid
utf-8 sequences if the custom
feature is activated:
use ;
use Duration;
let parser = with_time_units;
for in &
It's also possible to parse multiple durations at once with parse_multiple
. The different
durations can be separated by an optional delimiter
(a closure matching a u8
) defined with
parse_multiple
. If the delimiter is not encountered, a number also indicates a new duration.
use Duration;
use DurationParser;
let mut parser = new;
parser.parse_multiple;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
See also the examples folder for common recipes and integration with other crates. Run an example with
cargo run --example $FILE_NAME_WITHOUT_FILETYPE_SUFFIX
like the systemd time span parser example
# For some of the examples a help is available. To pass arguments to the example itself separate the arguments for cargo and the example with `--`
$ cargo run --example systemd --features custom --no-default-features -- --help
...
# To actually run the example execute
$ cargo run --example systemd --features custom --no-default-features '300ms20s 5day'
Original: 300ms20s 5day
μs: 432020300000
Human: 5d 20s 300ms
Time units
Second
is the default time unit (if not specified otherwise for example with
DurationParser::default_unit
) which is applied when no time unit was encountered in the input
string. The table below gives an overview of the constructor methods and which time units are
available. If a custom set of time units is required, DurationParser::with_time_units
can be used.
TimeUnit | Default identifier | Calculation | Default time unit |
---|---|---|---|
Nanosecond |
ns | 1e-9s |
☑ |
Microsecond |
Ms | 1e-6s |
☑ |
Millisecond |
ms | 1e-3s |
☑ |
Second |
s | SI definition | ☑ |
Minute |
m | 60s |
☑ |
Hour |
h | 60m |
☑ |
Day |
d | 24h |
☑ |
Week |
w | 7d |
☑ |
Month |
M | Year / 12 |
☐ |
Year |
y | 365.25d |
☐ |
Note that Months
and Years
are not included in the default set of time units. The current
implementation uses an approximate calculation of Months
and Years
in seconds and if they are
included in the final configuration, the Julian
year based calculation is used. (See table
above)
With the CustomDurationParser
from the custom
feature, the identifiers for time units can be
fully customized.
Customization
Unlike other crates, fundu
does not try to establish a standard for time units and their
identifiers or a specific number format. So, a lot of these aspects can be adjusted with ease when
initializing or building the parser. Here's an incomplete example for possible customizations of the
number format:
use Duration;
use *;
use ;
let parser = builder
// Use a custom set of time units. For demonstration purposes just NanoSecond = `ns`
.custom_time_units
// Allow some whitespace characters as delimiter between the number and the time unit
.allow_delimiter
// Makes the number optional. If no number was encountered `1` is assumed
.number_is_optional
// Disable parsing the fractional part of the number => 1.0 will return an error
.disable_fraction
// Disable parsing the exponent => 1e0 will return an error
.disable_exponent
// Finally, build a reusable DurationParser
.build;
// Some valid input
for in &
// Some invalid input
for in &
Here's an example for fully-customizable time units which uses the CustomDurationParser
from the
custom
feature:
use Duration;
use *;
use ;
let mut parser = with_time_units;
// Let's define a custom time unit `fortnight == 2 weeks` which isn't part of the basic
// [`TimeUnit`]s:
parser.custom_time_unit;
assert_eq!;
assert_eq!;
Benchmarks
To run the benchmarks on your machine, clone the repository
git clone https://github.com/Joining7943/fundu.git
cd fundu
and then run all benchmarks with
cargo bench --all-features
The iai-callgrind
(feature = with-iai
) and flamegraph
(feature = with-flamegraph
) benchmarks
can only be run on unix. Use the --features
option of cargo to run the benchmarks for specific
features:
cargo bench --features standard,custom,negative
The above won't run the flamegraph
and iai-callgrind
benchmarks.
Benchmarks can be further filtered for example with
cargo bench --bench benchmarks_standard
cargo bench --bench benchmarks_standard -- 'parsing speed'
cargo bench --features custom --no-default-features --bench benchmarks_custom
For more infos, see the help with
cargo bench --help # The cargo help for bench
cargo bench --bench benchmarks_standard -- --help # The criterion help
To get a rough idea about the parsing times, here the average parsing speed of some inputs on a comparatively slow machine (Quad core 3000Mhz, 8GB DDR3, Linux)
Input | avg parsing time | ~ samples / s |
---|---|---|
1 |
37.925 ns |
26_367_831.245 |
123456789.123456789 |
73.162 ns |
13_668_297.750 |
format!("{}.{}e-1022", "1".repeat(1022), "1".repeat(1022)) |
551.59 ns |
1_812_940.771 |
For comparison, fundu
's precision and additional features only add a very low performance overhead
for small and some mixed input and performs better than the reference function from the stdlib
as
the input gets larger (the reference function is Duration::from_secs_f64(input.parse().unwrap())
):
Input | avg parsing time | ~ samples / s |
---|---|---|
1 |
25.630 ns |
39_016_777.214 |
123456789.123456789 |
45.007 ns |
22_218_765.969 |
format!("{}.{}e-1022", "1".repeat(1022), "1".repeat(1022)) |
1.7457 µs |
572_836.111 |
The initialization for fixed size time unit sets with DurationParser::new
,
DurationParser::with_all_time_units
takes around 1-2 ns
and is negligibly small. The
initialization time for custom sets with DurationParser::with_time_units
has a maximum of around
10 ns
.
Comparison fundu
vs Duration::from_secs_f64
Here's a short incomplete overview of differences and advantages of fundu
over using
Duration::from_secs_f64(input.parse().unwrap())
(and
Duration::try_from_secs_f64(input.parse().unwrap())
)
Input | Result fundu |
Result Duration::(try_)from_secs_f64 |
---|---|---|
01271480964981728917.1 |
Duration::new(1_271_480_964_981_728_917, 100_000_000) |
Duration::new(1_271_480_964_981_729_024, 0) |
1.11111111111e10 |
Duration::new(11_111_111_111, 100_000_000) |
Duration::new(11_111_111_111, 100_000_381) |
1ns |
Duration::new(0, 1) |
cannot parse time units |
1000 |
When changing the default unit to MilliSecond -> Duration::new(1, 0) |
is always seconds based |
1e20 |
Duration::MAX |
panics or returns an error due to: can not convert float seconds to Duration: value is either too big or NaN |
infinity |
Duration::MAX |
panics or returns an error due to: can not convert float seconds to Duration: value is either too big or NaN |
fundu
has a small impact on performance when the input is small but performs better for large
input (See performance). Depending on the input data and if you need to parse a
massive amount of inputs and don't need the full precision or any of fundu
's features, you may
prefer using the native methods from the rust stdlib
.
Platform support
Since fundu
is purely built on top of the rust stdlib
without platform specific code, this
library should be compatible with all platforms. Please open an issue if you find any unsupported
platforms which rust
itself supports.
See also the CI
TODO
- Provide other year calculations:
- mean Gregorian year
- Sidereal year
- Tropical year
See also Changelog
License
MIT license (LICENSE or http://opensource.org/licenses/MIT)