10 个版本
0.2.7 | 2024年4月24日 |
---|---|
0.2.6 | 2023年9月29日 |
0.2.5 | 2023年6月27日 |
0.2.4 | 2023年5月4日 |
0.1.2 | 2021年1月5日 |
#12 在 日期和时间 中
60,884 每月下载量
用于 29 个 Crates (18 直接)
76KB
1.5K SLoC
ChronoUtil:Rust 的 Chrono crate 的强大扩展。
ChronoUtil 提供以下实用工具
RelativeDuration
:扩展 Chrono 的Duration
以添加月份和年份DateRule
:有用的迭代器,生成常规(例如每月)日期- 用于按月份和年份移动日期类型值的程序化辅助函数
它深受 Python 的 dateutil 启发,并提供类似的 API,但功能更少。
使用方法
将以下内容放入你的 Cargo.toml
[dependencies]
chronoutil = "0.2.7"
概述
RelativeDuration
ChronoUtils 使用一个 RelativeDuration
类型来表示时间跨度的大小,这个跨度可能不是绝对的(即不是简单地固定数量的纳秒)。相对持续时间由月份的数量和一个绝对 Duration
组件组成。
let one_day = RelativeDuration::days(1);
let one_month = RelativeDuration::months(1);
let delta = one_month + one_day;
let start = NaiveDate::from_ymd_opt(2020, 1, 1).unwrap();
assert_eq!(start + delta, NaiveDate::from_ymd_opt(2020, 2, 2).unwrap());
RelativeDuration
的行为在边缘情况下是一致且定义良好的(请参阅设计决策部分以获取解释)
let one_day = RelativeDuration::days(1);
let one_month = RelativeDuration::months(1);
let delta = one_month + one_day;
let start = NaiveDate::from_ymd_opt(2020, 1, 30).unwrap();
assert_eq!(start + delta, NaiveDate::from_ymd_opt(2020, 3, 1).unwrap());
相对持续时间还支持解析 ISO8601 规范的持续时间子集。例如
let payload = String::from("P1Y2M-3DT1H2M3.4S");
let parsed = RelativeDuration::parse_from_iso8601(&payload).unwrap();
assert_eq!(
parsed,
RelativeDuration::years(1)
+ RelativeDuration::months(2)
+ RelativeDuration::days(-3)
+ RelativeDuration::hours(1)
+ RelativeDuration::minutes(2)
+ RelativeDuration::seconds(3)
+ RelativeDuration::nanoseconds(400_000_000)
)
assert_eq!(parsed.format_to_iso8601().unwrap(), payload)
具体来说,我们要求所有字段除了秒之外都是整数。
DateRule
ChronoUtil 提供一个 DateRule
迭代器,可以可靠地生成定期间隔的日期集合。例如,以下代码将在 2025 年每月的最后一天生成一个 NaiveDate
let start = NaiveDate::from_ymd_opt(2025, 1, 31).unwrap();
let rule = DateRule::monthly(start).with_count(12);
// 2025-1-31, 2025-2-28, 2025-3-31, 2025-4-30, ...
Shift 函数
ChronoUtil 还公开了一些有用的 shift 函数,这些函数在内部使用,包括
shift_months
:将日期类型值按给定月份数量移动shift_years
:将日期类型值按给定年份数量移动with_year
:将日期类型值移动到给定的日期with_month
用于将日期值移动到指定的月份with_year
用于将日期值移动到指定的年份
设计决策和注意事项
我们更倾向于简单而不是复杂:我们只使用格里历,并且不对1500年以前的日期进行修改。
对于1日至28日之间的日期,按月移动具有明显的唯一含义,我们始终坚持这一点。1月28日之后的一个月总是2月28日。将2月28日再移动一个月将得到3月28日。
当移动没有等效日期的日期(例如,询问1月30日之后的一个月)时,我们首先计算目标月份,然后如果该月份没有对应日期,则将月份的最后一天作为结果。因此,在闰年,1月30日之后的一个月是2月29日。
RelativeDuration
的优先级顺序如下
- 如果按月移动,计算目标月份
- 如果初始日期在该月份不存在,则取该月份的最后一天
- 执行任何进一步的
Duration
移动
因此,将 1 个月和 1 天的 RelativeDuration
应用于 1 月 31 日,首先移动到 2 月的最后一天,然后添加一天,得到 3 月 1 日。应用于 1 月 30 日将得到相同的结果。
移动的日期没有从原始日期中保留 记忆。因此,如果我们将 1 月 31 日移动一个月并得到 2 月 28 日,则进一步移动一个月将是 3 月 28 日,而不是 3 月 31 日。
这引出了关于 RelativeDuration
的一个有趣的观点:加法不是 结合律
let start = NaiveDate::from_ymd_opt(2020, 1, 31).unwrap();
let delta = RelativeDuration::months(1);
let d1 = (start + delta) + delta;
let d2 = start + (delta + delta);
assert_eq!(d1, NaiveDate::from_ymd_opt(2020, 3, 29).unwrap());
assert_eq!(d2, NaiveDate::from_ymd_opt(2020, 3, 31).unwrap());
如果您想要一系列移动的日期,我们建议使用 DateRule
,它考虑了这些细微差别
let start = NaiveDate::from_ymd_opt(2020, 1, 31).unwrap();
let delta = RelativeDuration::months(1);
let mut rule = DateRule::new(start, delta);
assert_eq!(rule.next().unwrap(), NaiveDate::from_ymd_opt(2020, 1, 31).unwrap());
assert_eq!(rule.next().unwrap(), NaiveDate::from_ymd_opt(2020, 2, 29).unwrap());
assert_eq!(rule.next().unwrap(), NaiveDate::from_ymd_opt(2020, 3, 31).unwrap());
使用自定义 Datelike 类型
如果您有自己的自定义类型,该类型实现了 chrono 的 Datelike 特性,那么您已经可以使用所有移动函数(shift_months
、shift_year
)。
使用相对持续时间为您类型将涉及一些简单的样板代码。假设您的自定义日期类型 MyAwesomeUnicornDate
已经为 chrono 的 Duration
实现了 Add
,这将如下所示
impl Add<RelativeDuration> for MyAwesomeUnicornDate {
type Output = MyAwesomeUnicornDate;
#[inline]
fn add(self, rhs: RelativeDuration) -> MyAwesomeUnicornDate {
shift_months(self, rhs.months) + rhs.duration
}
}
依赖关系
~1MB
~18K SLoC