【A11】Duration —— 精确时长结构体实现

一个以毫秒(ms)为内部存储单位的精确时长类型,支持正负值,所有方法均为 const fn,可在编译期进行计算。


✨ 设计理念

命名约定

方法类型 命名风格 示例
构造方法 全写 from_millisfrom_secsfrom_mins
取值方法 全写(与构造对称) as_millisas_secsas_mins
链式操作 SI 单位符号 .ms().s().min().h().d()

设计原则:入口/出口清晰明确,中间操作简洁流畅。


🚀 快速开始

基本使用

rust 复制代码
use duration::Duration;

// 从不同单位构造
let d1 = Duration::from_secs(5);
let d2 = Duration::from_mins(2);
let d3 = Duration::from_hours(1);
let d4 = Duration::from_days(7);
let d5 = Duration::from_weeks(2);

// 零时长
let zero = Duration::zero();

链式操作

使用 SI 单位符号进行链式组合,语法简洁:

rust 复制代码
// 构建 2小时30分15秒500毫秒
let d = Duration::from_hours(2)
    .min(30)
    .s(15)
    .ms(500);

assert_eq!(d.as_millis(), 2 * 3_600_000 + 30 * 60_000 + 15 * 1000 + 500);

单位转换

rust 复制代码
let d = Duration::from_secs(3661);

assert_eq!(d.as_secs(), 3661);   // 3661秒
assert_eq!(d.as_mins(), 61);     // 61分钟
assert_eq!(d.as_hours(), 1);     // 1小时
assert_eq!(d.as_days(), 0);      // 0天

📚 API 文档

🏗️ 构造方法

方法 说明
from_millis(ms: i64) -> Self 从毫秒构造
from_secs(secs: i64) -> Self 从秒构造
from_mins(mins: i64) -> Self 从分钟构造
from_hours(hours: i64) -> Self 从小时构造
from_days(days: i64) -> Self 从天构造
from_weeks(weeks: i64) -> Self 从周构造
zero() -> Self 返回零时长

所有构造方法均为 const fn,支持编译期初始化:

rust 复制代码
const TIMEOUT: Duration = Duration::from_secs(30);
const ONE_DAY: Duration = Duration::from_days(1);

🔍 取值方法(单位转换)

方法 说明
as_millis(&self) -> i64 转换为毫秒
as_secs(&self) -> i64 转换为秒(向下取整)
as_mins(&self) -> i64 转换为分钟(向下取整)
as_hours(&self) -> i64 转换为小时(向下取整)
as_days(&self) -> i64 转换为天(向下取整)
as_weeks(&self) -> i64 转换为周(向下取整)

注意 :负值使用 div_euclid 进行向下取整,符合数学直觉

rust 复制代码
let d = Duration::from_secs(-3661);
assert_eq!(d.as_mins(), -62);   // -3661秒 = -61分1秒,向下取整为 -62分
assert_eq!(d.as_hours(), -2);   // -3661秒 = -1小时1分1秒,向下取整为 -2小时

✅ 判断方法

方法 说明
is_zero(&self) -> bool 是否为零
is_positive(&self) -> bool 是否为正数
is_negative(&self) -> bool 是否为负数
rust 复制代码
let d = Duration::from_secs(-5);
assert!(d.is_negative());
assert!(!d.is_positive());
assert!(!d.is_zero());

🔄 变换方法

方法 说明
abs(&self) -> Self 取绝对值
neg(&self) -> Self 取负值
rust 复制代码
let d = Duration::from_secs(-5);
assert_eq!(d.abs().as_secs(), 5);
assert_eq!(d.neg().as_secs(), 5);

⛓️ 链式方法(SI 单位符号)

方法 说明
.ms(ms: i64) -> Self 增加指定毫秒数
.s(secs: i64) -> Self 增加指定秒数
.min(mins: i64) -> Self 增加指定分钟数
.h(hours: i64) -> Self 增加指定小时数
.d(days: i64) -> Self 增加指定天

**周(week)**作为最大单位,仅由 from_weeks 构造,不提供链式方法:式方法:

rust 复制代码
// ✅ 正确:周作为起点
let d = Duration::from_weeks(2).d(3).h(4);

// ❌ 不存在:没有 .w() 方法
// let d = Duration::from_days(1).w(2);

➕ 运算Duration 支持以下运算符:运算符:

运算符 说明
+ 相加
- 相减
* 标量乘法(Duration * i64
/ 标量除法(Duration / i64
-(一元) 取负
rust 复制代码
let d1 = Duration::from_secs(3);
let d2 = Duration::from_secs(5);

assert_eq!((d1 + d2).as_secs(), 8);
assert_eq!((d2 - d1).as_secs(), 2);
assert_eq!((d1 * 2).as_secs(), 6);
assert_eq!((d2 / 2).as_secs(), 2);

assert_eq!((-d1).as_secs(), -3);

📊 迭代器求和

支持 Sum trait,可用于迭代器求和:

rust 复制代码
let durations = vec![
    Duration::from_secs(1),
    Duration::from_secs(2),
    Duration::from_secs(3),
];

let total: Duration = durations.iter().sum();
assert_eq!(total.as_secs(), 6);

⚡ 完整的 const 支持

所有方法均为 const fn,支持编译期计算:

rust 复制代码
const fn compute_timeout() -> Duration {
    Duration::from_secs(10)
        .s(5)
        .min(2)
}

const TIMEOUT: Duration = compute_timeout();

📋 特性总结

特性 支持
正负值
const fn(编译期计算)
Copy / Clone
Debug / Default
PartialEq / Eq
PartialOrd / Ord
运算符重载(+ - * / -)
迭代器求和(Sum)

💡 示例合集

rust 复制代码
use duration::Duration;

// 1. 构建复杂时长
let duration = Duration::from_days(3)
    .h(12)
    .min(30)
    .s(15)
    .ms(500);

// 2. 单位转换
assert_eq!(duration.as_millis(), 3 * 86_400_000 + 12 * 3_600_000 + 30 * 60_000 + 15 * 1000 + 500);
assert_eq!(duration.as_secs(), 3 * 86400 + 12 * 3600 + 30 * 60 + 15);
assert_eq!(duration.as_hours(), 3 * 24 + 12);

// 3. 负数处理
let negative = Duration::from_secs(-90);
assert_eq!(negative.as_mins(), -2);  // 向下取整

// 4. 取绝对值
assert_eq!(negative.abs().as_secs(), 90);

// 5. 算术运算
let total = Duration::from_hours(1) + Duration::from_mins(30);
assert_eq!(total.as_mins(), 90);

// 6. 常量定义
const FIVE_MINUTES: Duration = Duration::from_mins(5);
const TWO_HOURS: Duration = Duration::from_hours(2);

🎯 设计说明

为什么周没有链式方法?

周作为最大的时间单位,实际使用中极少需要"增加几周"的场景。周更适合作为起点 (通过 from_weeks 构造),而不是中间增量。这保持了链式方法的简洁性,只包含最常用的单位。

为什么 as_* 使用向下取整?

使用 div_euclid 而非普通除法,确保负数的行为符合数学直觉(向下取整),避免向零舍入带来的意外结果。

为什么用 ms 而非 millis 作为链式方法?

ms 是国际单位制(SI)的官方符号,与 .s().min().h().d() 保持风格统一,都采用 SI 单位符号。


📄 完整源码

rust 复制代码
// ============================================================
// duration.rs
// ============================================================

//! 精确时长(Duration)
//!
//! 以毫秒(ms)为内部存储单位的连续时间长度。
//!
//! # 设计约定
//! - 构造/取值方法使用全写:`from_millis` / `as_millis`
//! - 链式操作方法使用 SI 单位符号:`.ms()`, `.s()`, `.min()`, `.h()`, `.d()`
//!

use std::ops::{Add, Sub, Mul, Div, Neg};

// ===== 常量 =====
const MS_PER_SEC: i64 = 1000;
const MS_PER_MIN: i64 = 60 * MS_PER_SEC;
const MS_PER_HOUR: i64 = 60 * MS_PER_MIN;
const MS_PER_DAY: i64 = 24 * MS_PER_HOUR;
const MS_PER_WEEK: i64 = 7 * MS_PER_DAY;

/// 精确时长:以毫秒为单位的连续时间长度
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Duration {
    pub(crate) ms: i64,
}

impl Duration {
    // ===== 构造方法 =====
    pub const fn from_millis(ms: i64) -> Self { Self { ms } }
    pub const fn from_secs(secs: i64) -> Self { Self { ms: secs * MS_PER_SEC } }
    pub const fn from_mins(mins: i64) -> Self { Self { ms: mins * MS_PER_MIN } }
    pub const fn from_hours(hours: i64) -> Self { Self { ms: hours * MS_PER_HOUR } }
    pub const fn from_days(days: i64) -> Self { Self { ms: days * MS_PER_DAY } }
    pub const fn from_weeks(weeks: i64) -> Self { Self { ms: weeks * MS_PER_WEEK } }
    pub const fn zero() -> Self { Self { ms: 0 } }

    // ===== 视图方法 =====
    pub const fn as_millis(&self) -> i64 { self.ms }
    pub const fn as_secs(&self) -> i64 { self.ms.div_euclid(MS_PER_SEC) }
    pub const fn as_mins(&self) -> i64 { self.ms.div_euclid(MS_PER_MIN) }
    pub const fn as_hours(&self) -> i64 { self.ms.div_euclid(MS_PER_HOUR) }
    pub const fn as_days(&self) -> i64 { self.ms.div_euclid(MS_PER_DAY) }
    pub const fn as_weeks(&self) -> i64 { self.ms.div_euclid(MS_PER_WEEK) }

    pub const fn is_zero(&self) -> bool { self.ms == 0 }
    pub const fn is_positive(&self) -> bool { self.ms > 0 }
    pub const fn is_negative(&self) -> bool { self.ms < 0 }
    pub const fn abs(&self) -> Self { Self { ms: self.ms.abs() } }
    pub const fn neg(&self) -> Self { Self { ms: -self.ms } }

    // ===== 链式方法(周作为最大单位,仅由 from_weeks 构造) =====
    pub const fn s(mut self, secs: i64) -> Self { self.ms += secs * MS_PER_SEC; self }
    pub const fn min(mut self, mins: i64) -> Self { self.ms += mins * MS_PER_MIN; self }
    pub const fn h(mut self, hours: i64) -> Self { self.ms += hours * MS_PER_HOUR; self }
    pub const fn d(mut self, days: i64) -> Self { self.ms += days * MS_PER_DAY; self }
    pub const fn ms(mut self, ms: i64) -> Self { self.ms += ms; self }
}

// ===== 运算符重载 =====
impl Add<Duration> for Duration {
    type Output = Self;
    fn add(self, rhs: Duration) -> Self { Self { ms: self.ms + rhs.ms } }
}

impl Sub<Duration> for Duration {
    type Output = Self;
    fn sub(self, rhs: Duration) -> Self { Self { ms: self.ms - rhs.ms } }
}

impl Mul<i64> for Duration {
    type Output = Self;
    fn mul(self, rhs: i64) -> Self { Self { ms: self.ms * rhs } }
}

impl Div<i64> for Duration {
    type Output = Self;
    fn div(self, rhs: i64) -> Self { Self { ms: self.ms / rhs } }
}

impl Neg for Duration {
    type Output = Self;
    fn neg(self) -> Self { Self { ms: -self.ms } }
}

impl std::iter::Sum for Duration {
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(Duration::zero(), |acc, d| acc + d)
    }
}

impl<'a> std::iter::Sum<&'a Duration> for Duration {
    fn sum<I: Iterator<Item = &'a Duration>>(iter: I) -> Self {
        iter.fold(Duration::zero(), |acc, d| acc + *d)
    }
}

// ============================================================
// 测试
// ============================================================

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_construction() {
        assert_eq!(Duration::from_millis(1000).as_millis(), 1000);
        assert_eq!(Duration::from_secs(1).as_millis(), 1000);
        assert_eq!(Duration::from_mins(1).as_millis(), 60_000);
        assert_eq!(Duration::from_hours(1).as_millis(), 3_600_000);
        assert_eq!(Duration::from_days(1).as_millis(), 86_400_000);
        assert_eq!(Duration::from_weeks(1).as_millis(), 604_800_000);
        assert_eq!(Duration::zero().as_millis(), 0);
    }

    #[test]
    fn test_chain() {
        let d = Duration::from_hours(3).min(45).s(5);
        assert_eq!(d.as_millis(), 3 * 3_600_000 + 45 * 60_000 + 5 * 1000);

        let d = Duration::from_days(1).h(2).min(30);
        assert_eq!(d.as_millis(), 86_400_000 + 2 * 3_600_000 + 30 * 60_000);

        let d = Duration::from_weeks(2).d(3);
        assert_eq!(d.as_millis(), 2 * 604_800_000 + 3 * 86_400_000);

        let d = Duration::from_secs(1).ms(500);
        assert_eq!(d.as_millis(), 1500);
    }

    #[test]
    fn test_to_methods() {
        let d = Duration::from_secs(3661);
        assert_eq!(d.as_secs(), 3661);
        assert_eq!(d.as_mins(), 61);
        assert_eq!(d.as_hours(), 1);
        assert_eq!(d.as_days(), 0);

        // 负数向下取整
        let d = Duration::from_secs(-3661);
        assert_eq!(d.as_secs(), -3661);
        assert_eq!(d.as_mins(), -62);
        assert_eq!(d.as_hours(), -2);

        let d = Duration::from_millis(-500);
        assert_eq!(d.as_secs(), -1);
    }

    #[test]
    fn test_operators() {
        let d1 = Duration::from_secs(3);
        let d2 = Duration::from_secs(5);

        assert_eq!((d1 + d2).as_millis(), 8000);
        assert_eq!((d2 - d1).as_millis(), 2000);
        assert_eq!((d1 * 2).as_millis(), 6000);
        assert_eq!((d2 / 2).as_millis(), 2500);

        assert_eq!((-d1).as_millis(), -3000);
        assert_eq!(d1.neg().as_millis(), -3000);
    }

    #[test]
    fn test_abs() {
        let d = Duration::from_secs(-3);
        assert_eq!(d.abs().as_millis(), 3000);
        assert_eq!(d.abs().as_secs(), 3);
    }

    #[test]
    fn test_is_zero_positive_negative() {
        assert!(Duration::zero().is_zero());
        assert!(Duration::from_secs(1).is_positive());
        assert!(Duration::from_secs(-1).is_negative());
    }

    #[test]
    fn test_sum() {
        let durations = vec![
            Duration::from_secs(1),
            Duration::from_secs(2),
            Duration::from_secs(3),
        ];
        let total: Duration = durations.iter().sum();
        assert_eq!(total.as_secs(), 6);

        let total: Duration = durations.into_iter().sum();
        assert_eq!(total.as_secs(), 6);
    }
}
相关推荐
右耳朵猫AI_4 小时前
用 Rust 重写 Bun
rust·bun
独孤留白4 小时前
Rust 可变性完整指南 —— 从默认不可变到多线程安全修改
rust
hsg7711 小时前
简述:Rust、GeoRust、自主研发GIS平台
开发语言·后端·rust
AOwhisky21 小时前
下一代容器来了?Docker 宣布原生支持 WebAssembly
java·运维·docker·容器·rust·wasm
铅笔侠_小龙虾1 天前
Rust 学习(6)-所有权规则、移动语义、Clone 与 Copy
python·学习·rust
程序员爱钓鱼1 天前
Rust 控制流 if 详解:条件判断与 if 表达式
前端·后端·rust
花褪残红青杏小2 天前
Rust图像处理第19节-RGB 色彩矩阵变换:矩阵 × 向量 = 像素变换
rust·webassembly·图形学
white_ant2 天前
15-ASCII 转换工具使用指南
rust·tauri·watools
铅笔侠_小龙虾2 天前
Rust 学习(2)-变量、常量与 shadowing
学习·算法·rust