#日期-时间 #日期 #时间 #日历

chronoutil

Rust's Chrono crate 的强大扩展

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日期和时间

Download history 19704/week @ 2024-05-04 19638/week @ 2024-05-11 36894/week @ 2024-05-18 29884/week @ 2024-05-25 34298/week @ 2024-06-01 35385/week @ 2024-06-08 33256/week @ 2024-06-15 36644/week @ 2024-06-22 25291/week @ 2024-06-29 14990/week @ 2024-07-06 12038/week @ 2024-07-13 13110/week @ 2024-07-20 15089/week @ 2024-07-27 15272/week @ 2024-08-03 15104/week @ 2024-08-10 13016/week @ 2024-08-17

60,884 每月下载量
用于 29 个 Crates (18 直接)

MIT 许可证

76KB
1.5K SLoC

ChronoUtil:Rust 的 Chrono crate 的强大扩展。

ChronoUtil GitHub Actions ChronoUtil on crates.io ChronoUtil on docs.rs License: MIT

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 的优先级顺序如下

  1. 如果按月移动,计算目标月份
  2. 如果初始日期在该月份不存在,则取该月份的最后一天
  3. 执行任何进一步的 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_monthsshift_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