2 个版本

使用旧的 Rust 2015

0.4.11 2023 年 1 月 22 日
0.4.10 2020 年 1 月 27 日

#164 in 日期和时间


2 crates 中使用

MIT/Apache

785KB
11K SLoC

Chrono: Rust 的日期和时间

Chrono on Travis CI Chrono on Appveyor Chrono on crates.io Chrono on docs.rs Join the chat at https://gitter.im/chrono-rs/chrono

它旨在成为 time 库的完整超集。特别是,

  • Chrono 严格遵循 ISO 8601。
  • Chrono 默认具有时区意识,具有独立的非时区类型。
  • Chrono 空间最优且(虽然不是主要目标)效率合理。

之前曾尝试将优秀的日期和时间库引入 Rust,Chrono 建立在它们之上,并应该予以承认

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_stdDuration::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);

日期和时间有各种属性可供使用,可以单独修改。大多数属性都定义在 DatelikeTimelike 特性中,您应该在之前使用它们。加法和减法也受到支持。以下说明了大多数对日期和时间支持的操作

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_rfc2822to_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");

可以使用三种方法进行解析

  1. 标准 FromStr 特性(以及字符串上的 parse 方法)可用于解析 DateTime<FixedOffset>DateTime<Utc>DateTime<Local> 值。这解析了 {:?}std::fmt::Debug)格式指定符打印的内容,并要求存在偏移量。

  2. 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_rfc2822DateTime::parse_from_rfc3339 类似,但用于已知格式。

  3. 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、(不存在的)TimeDateTime 分别对应的本地版本,即 NaiveDateNaiveTimeNaiveDateTime

它们的接口几乎与其时区感知的孪生兄弟相同,但显然没有关联到时区,并且可以相当低级。它们主要用于构建更高层次类型的构建块。

时区感知的 DateTimeDate 类型有两个返回本地版本的方法: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