1 个不稳定版本

使用旧的 Rust 2015

0.4.10 2020 年 1 月 27 日

#403日期和时间

每月下载 23
用于 paseto-wasi07

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 目前使用 time 模块中的 Duration 类型来表示时间跨度的量级。由于这个类型与较新的标准持续时间类型具有相同的名称,因此参考文献将此类型称为 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>。当偏移量是输入的一部分且调用者无法猜测时,应使用此方法。它不能用于偏移量可能缺失的情况。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 文档以获取完整语法和规范符列表。

从和到纪元时间戳的转换

使用 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((2014, 1, 30).with_month((2)返回None

尚未支持高级时区处理。目前,您可以尝试使用Chrono-tz crate。

依赖项

~0.1–1.1MB
~18K SLoC