15 个版本 (4 个稳定版)
2.0.0 | 2023年8月7日 |
---|---|
1.2.0 | 2023年7月9日 |
1.1.0 | 2023年6月26日 |
0.5.1 | 2023年5月1日 |
0.5.0 | 2023年3月29日 |
#32 在 日期和时间
每月下载 24,237 次
用于 4 个 Crates (3 个直接使用)
415KB
6K SLoC
可配置、精确且快速的 Rust 字符串解析器,用于将字符串解析为持续时间
目录
概述
fundu
提供了一个灵活且快速的解析器,用于将 Rust 字符串转换为 Duration
。 fundu
解析成它自己的 Duration
,但提供了将数据转换为 std::time::Duration
、chrono::Duration
和 time::Duration
的方法。除非另有说明,否则此 README 描述的是主要的 fundu
软件包。以下是一些使用 standard
功能的有效的输入字符串示例
"1.41"
"42"
"2e-8"
、"2e+8"
(或类似地"2.0e8"
)".5"
或类似地"0.5"
"3."
或类似地"3.0"
"inf"
、"+inf"
、"infinity"
、"+infinity"
"1w"
(1周)或类似地"7d"
、"168h"
、"10080m"
、"604800s"
、……
以及自定义(或基本)功能,假设定义了一些自定义时间单位s
、secs
、minutes
、day
、days
、year
、years
、century
以及时间关键词yesterday
"1.41minutes"
或类似地,如果设置了allow_delimiter
则可以写成"1.41 minutes"
"years"
或类似地,如果设置了number_is_optional
则可以写成"1 years"
或"1years"
"42 secs ago"
或类似地,如果设置了allow_ago
和allow_negative
则可以写成"-42 secs"
"9e-3s"
、"9e3s"
(或类似地"9.0e+3s"
)"yesterday"
或类似地,如果设置了allow_negative
则可以写成"-1day"
或"-1days"
"9 century"
或类似地"900 years"
有关自定义功能的更多示例,请参阅自定义部分。本crate提供功能的摘要
- 精度:没有浮点计算,输入被精确解析,所以您输入的值就是您得到的值,在
Duration
的范围内。 - 性能:解析器速度极快(基准测试)
- 自定义:
TimeUnits
,数字格式和其他方面都易于配置(参见自定义) - 音量限制:如果输入数字大于该最大值或输入字符串为正
infinity
,则持续时间会饱和于Duration::MAX
。 - 负持续时间:解析器可以配置为解析负持续时间。Fundu的
Duration
可以表示负持续时间,并且实现了TryFrom
以支持chrono::Duration
和time::Duration
,前提是激活了相应的功能。 - 错误处理:错误信息力求信息丰富,但也可以轻松调整(参见示例)
fundu
旨在实现良好的性能和轻量级。它完全基于rust的stdlib
构建,标准配置中无需额外的依赖。默认接受的数字格式为科学浮点格式,与f64::from_str
兼容。然而,数字格式和其他方面可以根据需要自定义,例如与systemd时间跨度或GNU相对时间兼容的格式。有两个专门的、易于使用的fundu副项目
fundu-systemd
,用于完全兼容的systemd
时间跨度解析器fundu-gnu
,用于完全兼容的GNU
相对时间解析器。
有关详细信息,请参阅文档!
安装
要将fundu
添加到Cargo.toml
,请使用standard
功能。
[dependencies]
fundu = "2.0.0"
fundu分为三个主要功能,standard
(提供DurationParser
和parse_duration
)和custom
(提供CustomDurationParser
)以及base,用于更基本的核心解析方法。第一个在这里详细描述,
custom
功能为时间单位添加了完全可自定义的标识符。通常只需要一个解析器。例如,要仅包含CustomDurationParser
,请在Cargo.toml
中添加以下内容
[dependencies]
fundu = { version = "2.0.0", default-features = false, features = ["custom"] }
启用 chrono
或 time
功能,为 chrono::Duration
或 time::Duration
提供 TryFrom
和 SaturatingInto
实现。在无需额外功能的情况下,支持将 std::time::Duration
转换为/从转换。
启用 serde
功能允许某些结构和枚举使用 serde 进行序列化或反序列化。
示例
如果只需一次使用默认配置,可以使用 parse_duration
方法。注意,与其它解析器的 parse
方法不同,parse_duration
返回的是 std::time::Duration
,其它解析器的 parse
方法返回的是 fundu::Duration
。
use std::time::Duration;
use fundu::parse_duration;
let input = "1.0e2s";
assert_eq!(parse_duration(input).unwrap(), Duration::new(100, 0));
当需要自定义接受的时间单位(TimeUnit)时,可以使用 DurationParser::with_time_units
。
use fundu::{Duration, DurationParser};
let input = "3m";
assert_eq!(
DurationParser::with_all_time_units().parse(input).unwrap(),
Duration::positive(180, 0)
);
如果没有配置时间单位,则默认为秒。
use fundu::{Duration, DurationParser};
let input = "1.0e2";
assert_eq!(
DurationParser::without_time_units().parse(input).unwrap(),
Duration::positive(100, 0)
);
但是,以下将返回错误,因为 y
(年)不是默认时间单位。
use fundu::DurationParser;
let input = "3y";
assert!(DurationParser::new().parse(input).is_err());
解析器是可重用的,时间单位集可以完全自定义。
use fundu::TimeUnit::*;
use fundu::{Duration, DurationParser};
let parser = DurationParser::with_time_units(&[NanoSecond, Minute, Hour]);
assert_eq!(parser.parse("9e3ns").unwrap(), Duration::positive(0, 9000));
assert_eq!(parser.parse("10m").unwrap(), Duration::positive(600, 0));
assert_eq!(parser.parse("1.1h").unwrap(), Duration::positive(3960, 0));
assert_eq!(parser.parse("7").unwrap(), Duration::positive(7, 0));
如果将默认时间单位(如果没有在输入字符串中给出时间单位)设置为不同于秒的值,这也是容易实现的。
use fundu::TimeUnit::*;
use fundu::{Duration, DurationParser};
assert_eq!(
DurationParser::without_time_units()
.default_unit(MilliSecond)
.parse("1000")
.unwrap(),
Duration::positive(1, 0)
);
如果激活了 custom
功能,可以完全自定义时间单位的标识符,可以使用任意数量的有效 utf-8 序列。
use fundu::TimeUnit::*;
use fundu::{CustomTimeUnit, CustomDurationParser, Duration};
let parser = CustomDurationParser::with_time_units(&[
CustomTimeUnit::with_default(MilliSecond, &["χιλιοστό του δευτερολέπτου"]),
CustomTimeUnit::with_default(Second, &["s", "secs"]),
CustomTimeUnit::with_default(Hour, &["⏳"]),
]);
assert_eq!(parser.parse(".3χιλιοστό του δευτερολέπτου"), Ok(Duration::positive(0, 300_000)));
assert_eq!(parser.parse("1e3secs"), Ok(Duration::positive(1000, 0)));
assert_eq!(parser.parse("1.1⏳"), Ok(Duration::positive(3960, 0)));
可以使用 custom
功能来自定义更多内容。有关更多信息,请参阅 custom
功能的导出项的文档(例如 CustomTimeUnit
,TimeKeyword
)。
此外,fundu
尝试提供信息丰富的错误消息。
use fundu::DurationParser;
assert_eq!(
DurationParser::without_time_units()
.parse("1y")
.unwrap_err()
.to_string(),
"Time unit error: No time units allowed but found: 'y' at column 1"
);
数字格式可以轻松调整以满足您的需求。例如,允许数字是可选的,允许数字和时间单位之间有某些 ascii 空白,并将数字格式限制为整数,没有小数部分和指数(还请注意,DurationParserBuilder
可以在 const
上下文中编译时构建 DurationParser
)。
use fundu::TimeUnit::*;
use fundu::{Duration, DurationParser, ParseError};
const PARSER: DurationParser = DurationParser::builder()
.time_units(&[NanoSecond])
.allow_time_unit_delimiter()
.number_is_optional()
.disable_fraction()
.disable_exponent()
.build();
assert_eq!(PARSER.parse("ns").unwrap(), Duration::positive(0, 1));
assert_eq!(
PARSER.parse("1000\t\n\r ns").unwrap(),
Duration::positive(0, 1000)
);
assert_eq!(
PARSER.parse("1.0ns").unwrap_err(),
ParseError::Syntax(1, "No fraction allowed".to_string())
);
assert_eq!(
PARSER.parse("1e9ns").unwrap_err(),
ParseError::Syntax(1, "No exponent allowed".to_string())
);
还可以使用 parse_multiple
一次解析多个持续时间。不同的持续时间可以用空白和可选的连接词(此处为 and
)分隔。如果没有遇到分隔符,数字或符号字符也可以表示新的持续时间。
use fundu::{Duration, DurationParser};
let parser = DurationParser::builder()
.default_time_units()
.parse_multiple(Some(&["and"]))
.build();
assert_eq!(
parser.parse("1.5h 2e+2ns"),
Ok(Duration::positive(5400, 200))
);
assert_eq!(
parser.parse("55s500ms"),
Ok(Duration::positive(55, 500_000_000))
);
assert_eq!(parser.parse("1\t1"), Ok(Duration::positive(2, 0)));
assert_eq!(
parser.parse("1. .1"),
Ok(Duration::positive(1, 100_000_000))
);
assert_eq!(parser.parse("2h"), Ok(Duration::positive(2 * 60 * 60, 0)));
assert_eq!(
parser.parse("300ms20s 5d"),
Ok(Duration::positive(5 * 60 * 60 * 24 + 20, 300_000_000))
);
assert_eq!(
parser.parse("300.0ms and 5d"),
Ok(Duration::positive(5 * 60 * 60 * 24, 300_000_000))
);
有关常见配方和与其他 crate 集成的信息,请参阅示例文件夹。使用以下命令运行示例:
cargo run --example $FILE_NAME_WITHOUT_FILETYPE_SUFFIX
例如,systemd 时间范围解析器示例
# 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
时间单位
Second
是默认的时间单位(如果没有指定其他单位,例如使用 DurationParser::default_unit
),当输入字符串中没有遇到时间单位时应用。下表概述了构造函数方法和哪些时间单位可用。如果需要自定义一组时间单位,可以使用 DurationParser::with_time_units
。
时间单位 | 默认标识符 | 计算 | 默认时间单位 |
---|---|---|---|
纳秒 |
ns | 1e-9s |
☑ |
微秒 |
Ms | 1e-6s |
☑ |
毫秒 |
ms | 1e-3s |
☑ |
秒 |
s | SI定义 | ☑ |
分钟 |
m | 60s |
☑ |
小时 |
h | 60m |
☑ |
天 |
d | 24h |
☑ |
周 |
w | 7d |
☑ |
月 |
M | 年/ 12 |
☐ |
年 |
y | 365.25d |
☐ |
请注意,Months
和 Years
不包含在默认时间单位集中。当前实现使用对 Months
和 Years
的近似计算(以秒为单位),如果它们包含在最终配置中,则使用基于 儒略年 的计算。(见上表)
使用 CustomDurationParser
特性中的 CustomDurationParser
,可以完全自定义时间单位的标识符。
自定义
与其他crate不同,fundu
不试图建立时间单位及其标识符或特定数字格式的标准。许多这些方面可以在初始化或构建解析器时进行调整。以下是不完整的示例,说明可能的数字格式自定义
use fundu::TimeUnit::*;
use fundu::{Duration, DurationParser, ParseError};
let parser = DurationParser::builder()
// Use a custom set of time units. For demonstration purposes just NanoSecond
.time_units(&[NanoSecond])
// Allow some whitespace characters as delimiter between the number and the time unit
.allow_time_unit_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
assert_eq!(parser.parse("ns").unwrap(), Duration::positive(0, 1));
assert_eq!(
parser.parse("1000\t\n\r ns").unwrap(),
Duration::positive(0, 1000)
);
// Some invalid input
assert_eq!(
parser.parse("1.0ns").unwrap_err(),
ParseError::Syntax(1, "No fraction allowed".to_string())
);
assert_eq!(
parser.parse("1e9ns").unwrap_err(),
ParseError::Syntax(1, "No exponent allowed".to_string())
);
以下是一个使用 CustomDurationParser
特性中的 CustomDurationParser
的完全可定制时间单位的示例
use fundu::TimeUnit::*;
use fundu::{CustomDurationParser, CustomTimeUnit, Duration, Multiplier, TimeKeyword};
// Let's define a custom time unit `fortnight` which is worth 2 weeks. Note the creation
// of `CustomTimeUnits` and `TimeKeywords` can be `const` and moved to compile time:
const FORTNIGHT: CustomTimeUnit = CustomTimeUnit::new(
Week,
&["f", "fortnight", "fortnights"],
Some(Multiplier(2, 0)),
);
let parser = CustomDurationParser::builder()
.time_units(&[
CustomTimeUnit::with_default(Second, &["s", "secs", "seconds"]),
CustomTimeUnit::with_default(Minute, &["min"]),
CustomTimeUnit::with_default(Hour, &["ώρα"]),
FORTNIGHT,
])
// Additionally, define `tomorrow`, a keyword of time which is worth `1 day` in the future.
// In contrast to a `CustomTimeUnit`, a `TimeKeyword` doesn't accept a number in front of it
// in the source string.
.keyword(TimeKeyword::new(Day, &["tomorrow"], Some(Multiplier(1, 0))))
.build();
assert_eq!(
parser.parse("42e-1ώρα").unwrap(),
Duration::positive(15120, 0)
);
assert_eq!(
parser.parse("tomorrow").unwrap(),
Duration::positive(60 * 60 * 24, 0)
);
assert_eq!(
parser.parse("1fortnight").unwrap(),
Duration::positive(60 * 60 * 24 * 7 * 2, 0)
);
基准测试
要在您的机器上运行基准测试,请克隆存储库
git clone https://github.com/fundu-rs/fundu.git
cd fundu
然后运行所有基准测试
cargo bench --all-features
iai-callgrind
(特性 = with-iai
)和 flamegraph
(特性 = with-flamegraph
)基准测试只能在 Unix 上运行。使用 cargo 的 --features
选项运行特定特性的基准测试
cargo bench --features standard,custom
上面的命令不会运行 flamegraph
和 iai-callgrind
基准测试。
可以使用以下方式进一步筛选基准测试,例如
cargo bench --bench benchmarks_standard
cargo bench --bench benchmarks_standard -- 'parsing speed'
cargo bench --features custom --no-default-features --bench benchmarks_custom
有关更多信息,请使用帮助
cargo bench --help # The cargo help for bench
cargo bench --bench benchmarks_standard -- --help # The criterion help
为了大致了解解析时间,以下是某些输入的平均解析速度(四核 3000MHz,8GB DDR3,Linux)
输入 | 平均解析时间 |
---|---|
1 |
38.705ns |
123456789.123456789 |
57.974ns |
格式!("{0}.{0}e-1022", "1".重复(1022)) |
421.56ns |
1s |
55.755ns |
1ns |
59.842ns |
1y |
57.760ns |
贡献
贡献总是受欢迎!可以从现有的问题开始,或者打开一个新问题,以便我们可以讨论一切,这样就不会浪费任何努力。不要犹豫,提出问题!
使用 fundu 的项目
samply/beam
: https://github.com/samply/beamuutils/coreutils
: https://github.com/uutils/coreutilsjonhteper/minos
: https://github.com/jonhteper/minos
许可证
MIT 许可证(LICENSE 或 http://opensource.org/licenses/MIT)
依赖关系
~0–610KB
~11K SLoC