一个以毫秒(ms)为内部存储单位的精确时长类型,支持正负值,所有方法均为 const fn,可在编译期进行计算。
✨ 设计理念
命名约定
| 方法类型 | 命名风格 | 示例 |
|---|---|---|
| 构造方法 | 全写 | from_millis、from_secs、from_mins |
| 取值方法 | 全写(与构造对称) | as_millis、as_secs、as_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);
}
}