2 个版本
使用旧的 Rust 2015
0.4.11 | 2023 年 1 月 22 日 |
---|---|
0.4.10 | 2020 年 1 月 27 日 |
#164 in 日期和时间
在 2 crates 中使用
785KB
11K SLoC
Chrono: Rust 的日期和时间
它旨在成为 time 库的完整超集。特别是,
- Chrono 严格遵循 ISO 8601。
- Chrono 默认具有时区意识,具有独立的非时区类型。
- Chrono 空间最优且(虽然不是主要目标)效率合理。
之前曾尝试将优秀的日期和时间库引入 Rust,Chrono 建立在它们之上,并应该予以承认
- 维基百科上的初始研究
- Dietrich Epp 的 datetime-rs
- Luis de Bethencourt 的 rust-datetime
Chrono 中的任何重大更改都记录在 CHANGELOG.md
文件中。
用法
将以下内容放入你的 Cargo.toml
[dependencies]
chrono = "0.4"
或者,如果你想使用 Serde,可以这样包含功能
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
然后,将以下内容放入你的 crate 根目录
extern crate chrono;
避免使用 use chrono::*;
,因为 Chrono 除了类型之外还导出了一些模块。如果你更喜欢通配符导入,请使用以下替代方案
use chrono::prelude::*;
概览
持续时间
Chrono 目前使用 Duration
类型在 time
模块中代表时间段的量级。由于这个名称与较新的标准持续时间类型相同,因此参考将此类型称为 OldDuration
。请注意,这是一个“准确”的持续时间,表示为秒和纳秒,并不表示“名义”组件,例如天数或月份。
Chrono 目前还不原生支持标准 Duration
类型,但将来会支持。同时,您可以使用 Duration::from_std
和 Duration::to_std
方法在两种类型之间进行转换。
日期和时间
Chrono 提供了一个 DateTime
类型来表示带时区的日期和时间。
对于需要跟踪更抽象的时间点,例如不受时区影响的内部时间记录,可以考虑 SystemTime
,它跟踪系统时钟,或者 Instant
,它是一个不透明的但随时间单调递增的时间点表示。
DateTime
是时区感知的,并且必须从 TimeZone
对象中构造,该对象定义了如何将本地日期转换为 UTC 日期以及如何从 UTC 日期转换回来。有三个著名的 TimeZone
实现
-
Utc
指定 UTC 时区。这是最高效的。 -
Local
指定系统本地时区。 -
FixedOffset
指定一个任意、固定的时区,例如 UTC+09:00 或 UTC-10:30。这通常来自于解析后的文本日期和时间。由于它存储了最多的信息并且不依赖于系统环境,您可能希望将其他TimeZone
转换为此类型。
具有不同 TimeZone
类型的 DateTime
是不同的,不能混合,但可以使用 DateTime::with_timezone
方法进行转换。
您可以通过 Utc::now()
获取 UTC 时区的当前日期和时间,或通过 Local::now()
获取本地时区的当前日期和时间。
use chrono::prelude::*;
let utc: DateTime<Utc> = Utc::now(); // e.g. `2014-11-28T12:45:59.324310806Z`
let local: DateTime<Local> = Local::now(); // e.g. `2014-11-28T21:45:59.324310806+09:00`
或者,您可以创建自己的日期和时间。由于 Rust 缺乏函数和方法重载,这有点冗长,但反过来,我们得到了丰富的初始化方法组合。
use chrono::prelude::*;
use chrono::offset::LocalResult;
let dt = Utc.ymd(2014, 7, 8).and_hms(9, 10, 11); // `2014-07-08T09:10:11Z`
// July 8 is 188th day of the year 2014 (`o` for "ordinal")
assert_eq!(dt, Utc.yo(2014, 189).and_hms(9, 10, 11));
// July 8 is Tuesday in ISO week 28 of the year 2014.
assert_eq!(dt, Utc.isoywd(2014, 28, Weekday::Tue).and_hms(9, 10, 11));
let dt = Utc.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12); // `2014-07-08T09:10:11.012Z`
assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_micro(9, 10, 11, 12_000));
assert_eq!(dt, Utc.ymd(2014, 7, 8).and_hms_nano(9, 10, 11, 12_000_000));
// dynamic verification
assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(21, 15, 33),
LocalResult::Single(Utc.ymd(2014, 7, 8).and_hms(21, 15, 33)));
assert_eq!(Utc.ymd_opt(2014, 7, 8).and_hms_opt(80, 15, 33), LocalResult::None);
assert_eq!(Utc.ymd_opt(2014, 7, 38).and_hms_opt(21, 15, 33), LocalResult::None);
// other time zone objects can be used to construct a local datetime.
// obviously, `local_dt` is normally different from `dt`, but `fixed_dt` should be identical.
let local_dt = Local.ymd(2014, 7, 8).and_hms_milli(9, 10, 11, 12);
let fixed_dt = FixedOffset::east(9 * 3600).ymd(2014, 7, 8).and_hms_milli(18, 10, 11, 12);
assert_eq!(dt, fixed_dt);
日期和时间有各种属性可供使用,可以单独修改。大多数属性都定义在 Datelike
和 Timelike
特性中,您应该在之前使用它们。加法和减法也受到支持。以下说明了大多数对日期和时间支持的操作
extern crate time;
use chrono::prelude::*;
use chrono::Duration;
// assume this returned `2014-11-28T21:45:59.324310806+09:00`:
let dt = FixedOffset::east(9*3600).ymd(2014, 11, 28).and_hms_nano(21, 45, 59, 324310806);
// property accessors
assert_eq!((dt.year(), dt.month(), dt.day()), (2014, 11, 28));
assert_eq!((dt.month0(), dt.day0()), (10, 27)); // for unfortunate souls
assert_eq!((dt.hour(), dt.minute(), dt.second()), (21, 45, 59));
assert_eq!(dt.weekday(), Weekday::Fri);
assert_eq!(dt.weekday().number_from_monday(), 5); // Mon=1, ..., Sun=7
assert_eq!(dt.ordinal(), 332); // the day of year
assert_eq!(dt.num_days_from_ce(), 735565); // the number of days from and including Jan 1, 1
// time zone accessor and manipulation
assert_eq!(dt.offset().fix().local_minus_utc(), 9 * 3600);
assert_eq!(dt.timezone(), FixedOffset::east(9 * 3600));
assert_eq!(dt.with_timezone(&Utc), Utc.ymd(2014, 11, 28).and_hms_nano(12, 45, 59, 324310806));
// a sample of property manipulations (validates dynamically)
assert_eq!(dt.with_day(29).unwrap().weekday(), Weekday::Sat); // 2014-11-29 is Saturday
assert_eq!(dt.with_day(32), None);
assert_eq!(dt.with_year(-300).unwrap().num_days_from_ce(), -109606); // November 29, 301 BCE
// arithmetic operations
let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8);
assert_eq!(dt1.signed_duration_since(dt2), Duration::seconds(-2 * 3600 + 2));
assert_eq!(dt2.signed_duration_since(dt1), Duration::seconds(2 * 3600 - 2));
assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) + Duration::seconds(1_000_000_000),
Utc.ymd(2001, 9, 9).and_hms(1, 46, 40));
assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) - Duration::seconds(1_000_000_000),
Utc.ymd(1938, 4, 24).and_hms(22, 13, 20));
格式化和解析
格式化是通过 format
方法完成的,其格式与熟悉的 strftime
格式相同。
请参阅 format::strftime
文档以获取完整的语法和指定符列表。
默认的 to_string
方法以及 {:?}
指定符也提供了合理的表示。Chrono 还提供了 to_rfc2822
和 to_rfc3339
方法以支持已知格式。
use chrono::prelude::*;
let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9);
assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2014-11-28 12:00:09");
assert_eq!(dt.format("%a %b %e %T %Y").to_string(), "Fri Nov 28 12:00:09 2014");
assert_eq!(dt.format("%a %b %e %T %Y").to_string(), dt.format("%c").to_string());
assert_eq!(dt.to_string(), "2014-11-28 12:00:09 UTC");
assert_eq!(dt.to_rfc2822(), "Fri, 28 Nov 2014 12:00:09 +0000");
assert_eq!(dt.to_rfc3339(), "2014-11-28T12:00:09+00:00");
assert_eq!(format!("{:?}", dt), "2014-11-28T12:00:09Z");
// Note that milli/nanoseconds are only printed if they are non-zero
let dt_nano = Utc.ymd(2014, 11, 28).and_hms_nano(12, 0, 9, 1);
assert_eq!(format!("{:?}", dt_nano), "2014-11-28T12:00:09.000000001Z");
可以使用三种方法进行解析
-
标准
FromStr
特性(以及字符串上的parse
方法)可用于解析DateTime<FixedOffset>
、DateTime<Utc>
和DateTime<Local>
值。这解析了{:?}
(std::fmt::Debug
)格式指定符打印的内容,并要求存在偏移量。 -
DateTime::parse_from_str
解析带有偏移量的日期和时间,并返回DateTime<FixedOffset>
。当偏移量是输入的一部分且调用者无法猜测时,应使用此方法。它 不能 在偏移量可能缺失的情况下使用。《a href="https://docs.rs/chrono/0.4/chrono/struct.DateTime.html#method.parse_from_rfc2822" rel="ugc noopener">DateTime::parse_from_rfc2822
和DateTime::parse_from_rfc3339
类似,但用于已知格式。 -
Offset::datetime_from_str
类似,但返回给定偏移量的DateTime
。当输入中缺少显式偏移量时,它将使用给定的偏移量。如果输入包含与当前偏移量不同的显式偏移量,它将引发错误。
通过 format
模块可以更详细地控制解析过程。
use chrono::prelude::*;
let dt = Utc.ymd(2014, 11, 28).and_hms(12, 0, 9);
let fixed_dt = dt.with_timezone(&FixedOffset::east(9*3600));
// method 1
assert_eq!("2014-11-28T12:00:09Z".parse::<DateTime<Utc>>(), Ok(dt.clone()));
assert_eq!("2014-11-28T21:00:09+09:00".parse::<DateTime<Utc>>(), Ok(dt.clone()));
assert_eq!("2014-11-28T21:00:09+09:00".parse::<DateTime<FixedOffset>>(), Ok(fixed_dt.clone()));
// method 2
assert_eq!(DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"),
Ok(fixed_dt.clone()));
assert_eq!(DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"),
Ok(fixed_dt.clone()));
assert_eq!(DateTime::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone()));
// method 3
assert_eq!(Utc.datetime_from_str("2014-11-28 12:00:09", "%Y-%m-%d %H:%M:%S"), Ok(dt.clone()));
assert_eq!(Utc.datetime_from_str("Fri Nov 28 12:00:09 2014", "%a %b %e %T %Y"), Ok(dt.clone()));
// oops, the year is missing!
assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T %Y").is_err());
// oops, the format string does not include the year at all!
assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err());
// oops, the weekday is incorrect!
assert!(Utc.datetime_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err());
再次:请参阅 format::strftime
文档以获取完整的语法和指定符列表。
从和到 EPOCH 时间戳的转换
使用 Utc.timestamp(seconds, nanoseconds)
从 UNIX 时间戳(自 1970 年 1 月 1 日以来经过的秒和纳秒)构建 DateTime<Utc>
。
使用 DateTime.timestamp
获取从 DateTime
的时间戳(以秒为单位)。此外,您还可以使用 DateTime.timestamp_subsec_nanos
获取额外的纳秒数。
// We need the trait in scope to use Utc::timestamp().
use chrono::{DateTime, TimeZone, Utc};
// Construct a datetime from epoch:
let dt = Utc.timestamp(1_500_000_000, 0);
assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000");
// Get epoch value from a datetime:
let dt = DateTime::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap();
assert_eq!(dt.timestamp(), 1_500_000_000);
单独的日期
Chrono 还提供了单独的日期类型(Date
)。它也附带时区,并且必须通过时区来构造。大多数适用于 DateTime
的操作,只要适用,也适用于 Date
。
use chrono::prelude::*;
use chrono::offset::LocalResult;
assert_eq!(Utc::today(), Utc::now().date());
assert_eq!(Local::today(), Local::now().date());
assert_eq!(Utc.ymd(2014, 11, 28).weekday(), Weekday::Fri);
assert_eq!(Utc.ymd_opt(2014, 11, 31), LocalResult::None);
assert_eq!(Utc.ymd(2014, 11, 28).and_hms_milli(7, 8, 9, 10).format("%H%M%S").to_string(),
"070809");
由于缺乏实用性和复杂性,没有时区感知的 Time
。
DateTime
有一个 date
方法,它返回一个 Date
,代表其日期部分。还有一个 time
方法,它简单地返回以下描述的本地时间。
本地日期和时间
Chrono 提供了与 Date
、(不存在的)Time
和 DateTime
分别对应的本地版本,即 NaiveDate
、NaiveTime
和 NaiveDateTime
。
它们的接口几乎与其时区感知的孪生兄弟相同,但显然没有关联到时区,并且可以相当低级。它们主要用于构建更高层次类型的构建块。
时区感知的 DateTime
和 Date
类型有两个返回本地版本的方法:naive_local
返回对本地时间的视图,而 naive_utc
返回对 UTC 时间的视图。
限制
仅支持推前儒略历(即扩展以支持更早的日期)。如果您确实需要处理公元前20世纪的日期,请务必小心,它们可能是儒略历或其他历法。
日期类型在公历纪元大约 +/- 262,000 年内有限制。时间类型在纳秒精度上有限制。
表示中支持闰秒,但 Chrono 并不试图利用它们。(主要原因是闰秒不可预测。)几乎 所有 操作都会忽略可能的闰秒。如果您想使用隐含的 TAI(国际原子时)尺度,请考虑使用 NaiveDateTime
。
Chrono 本身不支持不精确或部分日期和时间的表示。任何可能产生歧义的操作将返回此类情况下的 None
。例如,“2014-01-30 之后的下一个月”没有明确定义,因此 Utc.ymd, 1, 30).with_month, 2)
返回 None
。
高级时区处理尚未支持。目前您可以尝试使用Chrono-tz crate。
依赖项
~0.1–1MB
~16K SLoC