Rust 里时间类型转换主要围绕chrono展开,也会涉及std::time、time,以及 DB/HTTP 边界上的互转。下面按常见场景整理,示例以chrono 0.4.x为准。
1. 标准库 ↔ chrono
use std::time::{SystemTime, UNIX_EPOCH}; use chrono::{DateTime, Utc}; // SystemTime -> DateTime<Utc> let st = SystemTime::now(); let dt: DateTime<Utc> = DateTime::<Utc>::from(st); // DateTime<Utc> -> SystemTime let st2: SystemTime = dt.into(); // 手动拿秒/纳秒 let dur = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("time before epoch"); let secs = dur.as_secs(); let nanos = dur.subsec_nanos();2. 时间戳 ↔ DateTime
use chrono::{TimeZone, Utc}; // DateTime -> 秒/毫秒时间戳 let now = Utc::now(); let ts_sec = now.timestamp(); let ts_ms = now.timestamp_millis(); // 秒时间戳 -> DateTime<Utc> let dt = Utc.timestamp_opt(ts_sec, 0) .single() .expect("invalid timestamp"); // 毫秒时间戳 -> DateTime<Utc> let dt_ms = Utc.timestamp_millis_opt(ts_ms) .single() .expect("invalid timestamp");注意:毫秒时间戳不要直接传给timestamp_opt(secs, nanos)的秒参数,否则会得到错误年份。
3. 字符串 ↔ NaiveDateTime / DateTime
use chrono::{DateTime, NaiveDateTime, Utc}; // NaiveDateTime 解析 let naive = NaiveDateTime::parse_from_str( "2026-10-31 12:30:00", "%Y-%m-%d %H:%M:%S", )?; // NaiveDateTime -> DateTime<Utc> let dt = Utc.from_utc_datetime(&naive); // DateTime -> 字符串 let s = dt.format("%Y-%m-%d %H:%M:%S").to_string(); // RFC3339 字符串解析 let dt2: DateTime<Utc> = "2026-10-31T04:30:00Z".parse()?; let rfc = dt2.to_rfc3339();如果字符串带时区,例如"2026-10-31 12:30:00 +08:00",可以用:
let dt = DateTime::parse_from_str( "2026-10-31 12:30:00 +08:00", "%Y-%m-%d %H:%M:%S %:z", )?; let utc = dt.with_timezone(&Utc);4. NaiveDateTime ↔ DateTime
use chrono::{DateTime, NaiveDateTime, Utc, Local, FixedOffset, TimeZone}; let naive = NaiveDateTime::parse_from_str( "2026-10-31 12:30:00", "%Y-%m-%d %H:%M:%S", )?; // 明确它是 UTC let utc: DateTime<Utc> = Utc.from_utc_datetime(&naive); // 明确它是本地时间 let local: DateTime<Local> = Local.from_local_datetime(&naive) .single() .expect("ambiguous or invalid local time"); // 转成固定时区,例如 UTC+8 let offset = FixedOffset::east_opt(8 * 3600) .expect("invalid offset"); let dt_offset: DateTime<FixedOffset> = utc.with_timezone(&offset);NaiveDateTime没有时区信息,所以不能直接“变成”DateTime<Tz>,必须说明它原本属于哪个时区。
5. 时区互转
use chrono::{DateTime, Utc}; use chrono_tz::Asia::Shanghai; let utc: DateTime<Utc> = Utc::now(); let shanghai = utc.with_timezone(&Shanghai);后端服务里比较稳妥的做法是:存储和内部传输用 UTC,展示时再转目标时区。
6. chrono ↔ time crate
如果项目里同时用了chrono和time,可以手动转:
use time::OffsetDateTime; use chrono::{DateTime, Utc}; // time -> chrono let odt: OffsetDateTime = OffsetDateTime::now_utc(); let ts = odt.unix_timestamp(); let dt: DateTime<Utc> = Utc.timestamp_opt(ts, odt.nanosecond()) .single() .expect("invalid timestamp"); // chrono -> time let now = Utc::now(); let odt2 = OffsetDateTime::from_unix_timestamp_nanos( now.timestamp_nanos_opt().expect("out of range") as i128, ).expect("invalid time");也可以用timetraveler这类小库做互转,但它不是标准方案,接入前要看维护状态。
7. DB / SeaORM / serde 场景
- SeaORM / sqlx:实体里常见
DateTime<Utc>、DateTime<FixedOffset>或chrono::NaiveDateTime,具体取决于字段类型和 feature。 - serde 序列化:给
chrono开启serdefeature 后,DateTime<Utc>默认通常按 RFC3339 序列化;如果需要自定义格式,可以用serde_with或手写serialize_with/deserialize_with。
工程化建议
- 内部统一用
DateTime<Utc>,边界层再做解析、格式化、时区转换。 - 解析外部时间一律走
Result,不要直接unwrap。 - 时间戳转换优先用
timestamp_opt、timestamp_millis_opt、single(),避免溢出和非法时间静默出错。 - 如果项目已经用 SeaORM,实体时间类型要和数据库列类型、
chronofeature 保持一致。
需要我帮你整理一份 SeaORM 实体中时间字段配置与序列化示例代码吗?
按chrono 0.4.x整理,优先列推荐写法;带opt/Result/Option的更适合生产代码,后端解析外部时间时尽量不要unwrap。
1. 时间戳 ↔ DateTime
| 方向 | 推荐函数 | 说明 |
|---|---|---|
| 秒时间戳 → DateTime | DateTime::from_timestamp(secs, nanos) | 返回Option<DateTime<Utc>> |
| 秒时间戳 → DateTime | Utc.timestamp_opt(secs, nanos) | 返回MappedLocalTime<DateTime<Utc>> |
| 毫秒时间戳 → DateTime | DateTime::from_timestamp_millis(millis) | 返回Option<DateTime<Utc>> |
| 毫秒时间戳 → DateTime | Utc.timestamp_millis_opt(millis) | 返回MappedLocalTime<DateTime<Utc>> |
| DateTime → 秒时间戳 | dt.timestamp() | 返回i64 |
| DateTime → 毫秒时间戳 | dt.timestamp_millis() | 返回i64 |
| DateTime → 微秒时间戳 | dt.timestamp_micros() | 返回i64 |
| DateTime → 纳秒时间戳 | dt.timestamp_nanos_opt() | 返回Option<i64> |
2. 字符串 ↔ 时间对象
| 方向 | 推荐函数 | 说明 |
|---|---|---|
| 自定义格式 → NaiveDateTime | NaiveDateTime::parse_from_str(s, fmt) | 无时区 |
| 自定义格式 → DateTime | DateTime::parse_from_str(s, fmt) | 字符串需带时区 |
| RFC3339 → DateTime | DateTime::parse_from_rfc3339(s) | 如2026-10-31T12:00:00+08:00 |
| RFC2822 → DateTime | DateTime::parse_from_rfc2822(s) | 邮件/HTTP 场景常见 |
| 字符串 → DateTime | s.parse::<DateTime<Utc>>() | 适合...Z结尾 |
| 时间 → 字符串 | dt.format(fmt).to_string() | strftime 风格 |
| 时间 → RFC3339 | dt.to_rfc3339() | API 常用 |
| 时间 → RFC2822 | dt.to_rfc2822() | 邮件/协议常用 |
3. Naive ↔ DateTime / 时区转换
| 方向 | 推荐函数 | 说明 |
|---|---|---|
| NaiveDateTime → DateTime | Utc.from_utc_datetime(&naive) | 明确它是 UTC |
| NaiveDateTime → DateTime | tz.from_local_datetime(&naive) | 返回MappedLocalTime |
| DateTime → NaiveDateTime | dt.naive_utc() | 丢弃时区,取 UTC 字面值 |
| DateTime → NaiveDateTime | dt.naive_local() | 取本地字面值 |
| 时区切换 | dt.with_timezone(&tz) | 不改变绝对时间点 |
| 转 UTC | dt.to_utc() | 得到DateTime<Utc> |
| 转固定偏移 | dt.fixed_offset() | 得到DateTime<FixedOffset> |
4. 标准库 ↔ chrono
| 方向 | 推荐函数 | 说明 |
|---|---|---|
| SystemTime → DateTime | DateTime::<Utc>::from(st) | 直接转换 |
| DateTime → SystemTime | dt.into() | 直接转换 |
| SystemTime → 秒数 | st.duration_since(UNIX_EPOCH) | 返回Result<Duration> |
| 秒数 → SystemTime | UNIX_EPOCH + Duration::from_secs(secs) | 手动构造 |
5. 日期/时间片段的组合
| 方向 | 推荐函数 | 说明 |
|---|---|---|
| NaiveDate → NaiveDateTime | date.and_time(time) | 组合日期和时间 |
| NaiveDateTime → NaiveDate | dt.date() | 取日期部分 |
| NaiveDateTime → NaiveTime | dt.time() | 取时间部分 |
| NaiveDateTime → DateTime | naive.and_utc() | 明确按 UTC 解释 |
| NaiveDateTime → DateTime | naive.and_local_timezone(Local) | 可能歧义或缺失 |
6. 建议少用或避免的写法
NaiveDateTime::from_timestamp*:已弃用,优先用DateTime::from_timestamp*。DateTime::from_utc/DateTime::from_local:旧 API,优先用from_naive_utc_and_offset、from_utc_datetime、from_local_datetime。timestamp_nanos():可能溢出,优先用timestamp_nanos_opt()。- 解析外部时间直接
unwrap():应走?、match或ok()?。
工程化小约定
- 存储、日志、跨服务传输统一用
DateTime<Utc>。 - 用户输入、前端展示、本地报表再做时区转换。
- 所有解析入口统一包一层
Result<DateTime<Utc>, AppTimeError>,避免每个 handler 重复写格式判断。
需要我提供一段统一的 DateTime 解析封装代码示例,方便你直接集成到项目里吗?