Rust 时间处理神器:chrono 从入门到实战
在 Rust 生态中,chrono 凭借其遵循 ISO 8601 标准、支持时区、类型安全且性能优异的特性,成为了 Rust 开发者的首选工具。本文将从入门到进阶,并结合实际开发场景带你全面掌握 chrono 的使用方法,以及避开时间处理中的那些"坑",让你在项目中轻松搞定时间操作。
为什么选择 chrono
Rust 标准库中其实提供了基础的时间类型(如 std::time::SystemTime),但是功能比较简陋,不支持时区处理、复杂时间运算和格式化,无法满足实际项目的需求。而 chrono 则完美弥补了这一短板。
chrono 默认支持时区处理,区分时区相关(DateTime<Tz>)和无时区(Naive 系列)类型。同时遵循 ISO 8601 国际标准,支持 RFC 2822、RFC 3339 等常见时间格式的解析与格式化。而且支持与 serde、chrono-tz 等库集成,覆盖更多复杂场景(如自定义时区、序列化)。
快速入门
首先在 Cargo.toml 中添加 chrono 依赖:
toml
[dependencies]
chrono = "0.4"
示例代码如下:
rust
use chrono::{DateTime, Local, Utc};
fn main() {
// 获取当前 UTC 时间(推荐用于存储和跨系统交互)
let utc_now: DateTime<Utc> = Utc::now();
println!("当前 UTC 时间:{}", utc_now);
// 获取当前系统本地时间
let local_now: DateTime<Local> = Local::now();
println!("当前本地时间:{}", local_now);
// 输出格式化时间(自定义格式)
println!(
"本地时间格式化:{}",
local_now.format("%Y年%m月%d日 %H时%M分%S秒")
);
}
运行代码会输出如下结果:
plaintext
当前 UTC 时间:2026-04-07 10:42:59.386671 UTC
当前本地时间:2026-04-07 18:42:59.386908 +08:00
本地时间格式化:2026年04月07日 18时42分59秒
chrono 类型解析
无时区类型(Naive 系列)
"Naive" 意为"朴素的",这类类型不包含时区信息,仅表示"年-月-日-时-分-秒"的组合,适用于不需要时区的场景,比如本地日志等。
NaiveDate:仅表示日期(年-月-日),无时间和时区。NaiveTime:仅表示时间(时-分-秒-纳秒),无日期和时区。NaiveDateTime:表示日期+时间,无时区,是NaiveDate和NaiveTime的组合。
这里我们写一个创建和操作 Naive 系列类型的示例:
rust
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
fn main() {
// 创建 NaiveDate(安全构造,返回 Option,避免 panic)
let date = NaiveDate::from_ymd_opt(2026, 4, 7).unwrap();
println!("NaiveDate: {}", date);
// 创建 NaiveTime
let time = NaiveTime::from_hms_opt(9, 30, 0).unwrap();
println!("NaiveTime: {}", time);
// 组合为 NaiveDateTime
let naive_dt = NaiveDateTime::new(date, time);
println!("NaiveDateTime: {}", naive_dt);
// 无效日期(返回 None,避免运行时崩溃)
let invalid_date = NaiveDate::from_ymd_opt(2026, 13, 32);
println!("无效日期: {:?}", invalid_date); // None
}
时区相关类型(DateTime<Tz>)
DateTime<Tz> 是 chrono 中最常用的类型,其中 Tz 是时区泛型参数,用于指定具体的时区。chrono 内置了三种常用时区实现:
Utc:协调世界时(零时区),最推荐用于存储和跨系统通信,避免时区混乱。Local:系统本地时区,运行时动态获取,适用于本地时间展示。FixedOffset:固定时区偏移(如 UTC+8、UTC-5),适用于简单的时区场景。
时区转换与操作的示例如下:
rust
use chrono::{FixedOffset, Local, Utc};
fn main() {
// 获取当前 UTC 时间
let utc_now = Utc::now();
println!("UTC 时间: {}", utc_now);
// 转换为本地时区
let local_now = utc_now.with_timezone(&Local);
println!("本地时区时间: {}", local_now);
// 转换为固定时区(UTC+8,如北京、上海时区)
let beijing_tz = FixedOffset::east_opt(8 * 3600).unwrap(); // 8小时 * 3600秒/小时
let beijing_now = utc_now.with_timezone(&beijing_tz);
println!("北京时区时间: {}", beijing_now);
// 转换为纽约时区(UTC-5)
let new_york_tz = FixedOffset::west_opt(5 * 3600).unwrap();
let new_york_now = utc_now.with_timezone(&new_york_tz);
println!("纽约时区时间: {}", new_york_now);
}
需要注意的是不同时区的 DateTime 类型是不同的,比如 DateTime<Utc> 和 DateTime<Local> 不能直接比较,需通过 with_timezone 方法转换到同一时区后再操作。
时间间隔(Duration)
Duration 用于表示两个时间点之间的间隔,支持秒、毫秒、微秒、纳秒等精度,可与 DateTime、NaiveDateTime 进行加减运算。
rust
use chrono::{Duration, Utc};
use std::time::Duration as StdDuration;
fn main() {
let now = Utc::now();
// 创建 chrono 的 Duration(1天 + 2小时 + 30分钟)
let duration = Duration::days(1) + Duration::hours(2) + Duration::minutes(30);
// 时间加法:当前时间 + 时间间隔
let future = now + duration;
println!("1天2小时30分钟后: {}", future);
// 时间减法:当前时间 - 时间间隔
let past = now - duration;
println!("1天2小时30分钟前: {}", past);
// 计算两个时间点的间隔
let diff = future - past;
println!(
"时间间隔: {}天 {}小时",
diff.num_days(),
diff.num_hours() % 24
);
// 与标准库 Duration 转换
let std_duration = StdDuration::from_secs(3600);
let chrono_duration = Duration::from_std(std_duration).unwrap();
println!("标准库1小时转换为 chrono Duration: {:?}", chrono_duration);
}
实战场景
时间格式化与解析
chrono 支持多种标准格式(RFC 2822、RFC 3339)和自定义格式,格式化使用 format 方法,解析使用 parse_from_str、parse_from_rfc3339 等方法。
rust
use chrono::{DateTime, NaiveDateTime, ParseError, Utc};
fn main() -> Result<(), ParseError> {
let now = Utc::now();
// 标准格式输出
println!("RFC 3339 格式: {}", now.to_rfc3339());
println!("RFC 2822 格式: {}", now.to_rfc2822());
// 自定义格式输出
let custom_format = now.format("%Y-%m-%d %H:%M:%S.%3f").to_string();
println!("自定义格式(保留3位毫秒): {}", custom_format);
// 解析字符串为 DateTime
let dt_str = "2026-04-07 09:30:00";
let naive_dt = NaiveDateTime::parse_from_str(dt_str, "%Y-%m-%d %H:%M:%S")?;
println!("解析自定义格式: {}", naive_dt);
// 解析 RFC 3339 格式字符串(带时区)
let rfc3339_str = "2026-04-07T09:30:00+08:00";
let dt = DateTime::parse_from_rfc3339(rfc3339_str)?;
println!("解析 RFC 3339 格式: {}", dt);
// 解析为 UTC 时间
let utc_dt: DateTime<Utc> = "2026-04-07T01:30:00Z".parse()?;
println!("解析为 UTC 时间: {}", utc_dt);
Ok(())
}
时间戳与 DateTime 转换
时间戳(Unix Timestamp)是指从 1970-01-01 00:00:00 UTC 到当前时间的秒数(或毫秒数),是跨系统时间传递的常用格式,chrono 支持时间戳与 DateTime、NaiveDateTime 的双向转换。
rust
use chrono::{TimeZone, Utc};
fn main() {
// DateTime 转换为时间戳(秒)
let utc_now = Utc::now();
let timestamp_sec = utc_now.timestamp(); // i64 类型,秒级时间戳
println!("当前 UTC 时间戳(秒): {}", timestamp_sec);
// DateTime 转换为毫秒级时间戳
let timestamp_ms = utc_now.timestamp_millis();
println!("当前 UTC 时间戳(毫秒): {}", timestamp_ms);
// 时间戳(秒)转换为 DateTime<Utc>
let dt_from_sec = Utc.timestamp_opt(timestamp_sec, 0).unwrap();
println!("秒级时间戳转换回 DateTime: {}", dt_from_sec);
// 时间戳(毫秒)转换为 DateTime<Utc>
let dt_from_ms = Utc.timestamp_millis_opt(timestamp_ms).unwrap();
println!("毫秒级时间戳转换回 DateTime: {}", dt_from_ms);
}
复杂时间运算与判断
在实际开发中,有时候需要判断某个时间是否在指定区间、计算两个时间的差值、获取某个时间的前后时间点等,chrono 提供了丰富的方法支持。
rust
use chrono::{Datelike, Duration, NaiveDate, Utc};
fn main() {
let now = Utc::now();
// 判断时间是否在指定区间内
let start = now - Duration::hours(1);
let end = now + Duration::hours(1);
let target = now - Duration::minutes(30);
let is_in_range = target >= start && target <= end;
println!("目标时间是否在 [1小时前, 1小时后] 区间: {}", is_in_range);
// 获取指定时间的前后时间点(安全方法,避免溢出)
// 前一天
let yesterday = now.checked_sub_signed(Duration::days(1)).unwrap();
// 后一周
let next_week = now.checked_add_signed(Duration::weeks(1)).unwrap();
println!("昨天此时: {}", yesterday);
println!("一周后此时: {}", next_week);
// 获取日期的详细信息(年、月、日、星期等)
println!("当前年份: {}", now.year());
println!("当前月份: {}", now.month()); // 1-12
println!("当前日期: {}", now.day());
println!("当前星期: {:?}", now.weekday()); // 枚举类型,如 Mon、Tue
println!("本月第几天: {}", now.day0()); // 0-30,本月第一天为 0
println!("今年第几天: {}", now.ordinal()); // 1-366(闰年)
// 计算两个日期的天数差
let date1 = NaiveDate::from_ymd_opt(2026, 4, 7).unwrap();
let date2 = NaiveDate::from_ymd_opt(2026, 12, 31).unwrap();
let days_diff = (date2 - date1).num_days();
println!("2026-04-07 到 2026-12-31 的天数差: {}", days_diff);
}
避坑指南
忽视时区,直接使用 Local 存储时间
在开发过程中,将 DateTime<Local> 存储到数据库或用于跨系统交互的场景下,这样是会有问题的,当涉及到跨时区的时候就会导致时间错乱,如果遇到夏令时切换的话则造成时间重复或跳跃的问题。正确的做法应该是存储时使用 DateTime<Utc>,展示时再转换为本地时区。
直接比较不同时区的 DateTime
不同时区的 DateTime 是不能参与比较的,比如 Utc::now() > Local::now(),这直接就会报错。正确的做法应该是先将所有时间转换到同一时区(如 UTC),再进行比较。
总结
chrono 作为 Rust 时间处理的"瑞士军刀",覆盖了从基础时间操作到复杂跨时区场景的几乎所有需求,其类型安全、标准兼容、性能优异的特性,使其成为 Rust 项目的首选时间库。