Skip to main content

wist_shared/
time.rs

1//! Shared time helpers.
2
3use time::OffsetDateTime;
4use time::format_description::well_known::Rfc3339;
5
6/// 当前 UTC 时刻的 RFC 3339 字符串(如 `2026-09-23T20:24:24Z`)。
7pub fn now_rfc3339() -> String {
8    OffsetDateTime::now_utc()
9        .format(&Rfc3339)
10        .expect("format RFC3339 timestamp")
11}
12
13/// 当前 Unix 时间戳(毫秒)。
14pub fn now_ts_ms() -> i64 {
15    OffsetDateTime::now_utc().unix_timestamp_nanos() as i64 / 1_000_000
16}
17
18/// `millis` 毫秒之后的 RFC 3339 时刻。
19///
20/// 用 `saturating_add` 而不是 `+`:`+` 在越界时会 panic,而 `millis` 是 `u64`,可能来自外部
21/// 配置(如计划的 `max_total_duration_ms`)。超过 `i64` 毫秒表示范围时按 `i64::MAX` 处理,
22/// 最终饱和到能表示的最大时刻,绝不 panic。
23pub fn after_millis_rfc3339(millis: u64) -> String {
24    let delta = time::Duration::milliseconds(i64::try_from(millis).unwrap_or(i64::MAX));
25    OffsetDateTime::now_utc()
26        .saturating_add(delta)
27        .format(&Rfc3339)
28        .expect("format RFC3339 timestamp")
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    fn parse(value: &str) -> OffsetDateTime {
36        OffsetDateTime::parse(value, &Rfc3339).expect("parse rfc3339")
37    }
38
39    fn to_ms(value: OffsetDateTime) -> i128 {
40        value.unix_timestamp_nanos() / 1_000_000
41    }
42
43    #[test]
44    fn now_rfc3339_is_parseable_and_close_to_now() {
45        let before = OffsetDateTime::now_utc();
46        let parsed = parse(&now_rfc3339());
47        let after = OffsetDateTime::now_utc();
48        assert!(parsed >= before - time::Duration::seconds(1));
49        assert!(parsed <= after + time::Duration::seconds(1));
50    }
51
52    #[test]
53    fn now_ts_ms_is_a_plausible_epoch_millisecond() {
54        // 2020-01-01 至 2100-01-01 的窗口:宽松,但能抓住秒/微秒/纳秒量级错误。
55        let ms = now_ts_ms();
56        assert!(ms > 1_577_836_800_000, "看起来不是毫秒时间戳: {ms}");
57        assert!(ms < 4_102_444_800_000, "看起来不是毫秒时间戳: {ms}");
58    }
59
60    #[test]
61    fn after_millis_zero_is_now_and_positive_moves_forward() {
62        let start = to_ms(OffsetDateTime::now_utc());
63
64        let at_zero = to_ms(parse(&after_millis_rfc3339(0)));
65        assert!(
66            (at_zero - start).abs() < 2_000,
67            "偏离过大: {at_zero} vs {start}"
68        );
69
70        let plus = to_ms(parse(&after_millis_rfc3339(60_000)));
71        assert!(
72            (plus - start - 60_000).abs() < 2_000,
73            "应大约晚 60s: {plus} vs {start}"
74        );
75    }
76
77    #[test]
78    fn after_millis_saturates_instead_of_panicking_on_huge_inputs() {
79        // 旧实现用 `+`:越界会 panic,且 `millis as i64` 对 u64::MAX 会回绕成负数。
80        // 现在应当饱和到一个未来时刻,且始终是可格式化的 RFC 3339。
81        for millis in [u64::MAX, i64::MAX as u64, (i64::MAX as u64) + 1, 1u64 << 62] {
82            let text = after_millis_rfc3339(millis);
83            let parsed = parse(&text);
84            assert!(
85                parsed >= OffsetDateTime::now_utc(),
86                "{millis} 应装出未来时刻,实际 {text}"
87            );
88        }
89    }
90}