跳过正文

chrono

··4003 字
Rust Rust-Crate
目录
rust crate - 这篇文章属于一个选集。
§ 3: 本文

chrono crate 提供了 Date/Time 相关类型和操作。

chrono 提供的类型:

  1. NaiveDate/NaiveTime/NaiveDateTime: 不带时区信息的 Date/Time/DateTime;
  2. DateTime<Utc>: 带有时区的日期和时间;

1 Native
#

NaiveDate/NaiveTime/DaiveDateTime 都是没有时区信息的日期、时间对象, 主要使用场景是 DB 的 DATE/DATETIME 类型字段。

  • from_xx_opt(): 创建 Naive 对象;
  • and_xx_opt(): 为 NavieDate 补充 time 信息,返回一个新的 NaiveDateTime 对象;
  • checked_add_xx()/checked_sub_xx(): 为对象添加、减少对应的日期、时间;

建议使用 xx_opt() 结尾的方法,它们返回 Option 类型,在传入的参数有误时返回 None。

use chrono::{NaiveDate, NaiveDateTime, NaiveTime};


let d = NaiveDate::from_ymd_opt(2015, 6, 3).unwrap();
let t = NaiveTime::from_hms_milli_opt(12, 34, 56, 789).unwrap();
let dt = NaiveDateTime::new(d, t);
assert_eq!(dt.date(), d);
assert_eq!(dt.time(), t);

let dt: NaiveDateTime = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_opt(9, 10, 11).unwrap();
assert_eq!(dt.time(), NaiveTime::from_hms_opt(9, 10, 11).unwrap());

assert!(from_ymd_opt(2015, 3, 14).is_some());
assert!(from_ymd_opt(2015, 0, 14).is_none());
assert!(from_ymd_opt(2015, 2, 29).is_none());
assert!(from_ymd_opt(-4, 2, 29).is_some()); // 5 BCE is a leap year
assert!(from_ymd_opt(400000, 1, 1).is_none());
assert!(from_ymd_opt(-400000, 1, 1).is_none());

从 Naive 对象创建 DateTime<TZ>:

  • .and_local_timezone(tz): 给 Navie 添加 timezone 信息,返回 DateTime<FixedOffset> 对象;
  • .and_utc(): 给 NaiveDate 添加 UTC 信息,返回 DateTime<Utc> 对象;
use chrono::{FixedOffset, NaiveDate};
let hour = 3600;
let tz = FixedOffset::east_opt(5 * hour).unwrap();
let dt = NaiveDate::from_ymd_opt(2015, 9, 5)
    .unwrap()
    .and_hms_opt(23, 56, 4)
    .unwrap()
    // 给 NaiveDate 添加上 timezone 信息(日期、时间都不变),返回 DateTime<FixedOffset> 类型
    .and_local_timezone(tz)
    .unwrap();
assert_eq!(dt.timezone(), tz);
println!("dt: {:?}", dt); // dt: 2015-09-05T23:56:04+05:00

// and_utc() 给 NaiveDate 添加 UTC 信息,返回 DateTime<Utc>
use chrono::{NaiveDate, Utc};
let dt = NaiveDate::from_ymd_opt(2023, 1, 30).unwrap().and_hms_opt(19, 32, 33).unwrap().and_utc();
assert_eq!(dt.timezone(), Utc);
println!("utc: {:?}", dt); // utc: 2023-01-30T19:32:33Z

解析:从字符串解析时忽略时区和偏移信息。

use chrono::NaiveDate;

let parse_from_str = NaiveDate::parse_from_str;

assert_eq!(
    parse_from_str("2015-09-05", "%Y-%m-%d"),
    Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap())
);
assert_eq!(
    parse_from_str("5sep2015", "%d%b%Y"),
    Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap())
);

// Time and offset is ignored for the purpose of parsing.
assert_eq!(
    parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"),
    Ok(NaiveDate::from_ymd_opt(2014, 5, 17).unwrap())
);


// NaiveDate 实现了 FromStr
use chrono::NaiveDate;
let d = NaiveDate::from_ymd_opt(2015, 9, 18).unwrap();
assert_eq!("2015-09-18".parse::<NaiveDate>(), Ok(d));
let d = NaiveDate::from_ymd_opt(12345, 6, 7).unwrap();
assert_eq!("+12345-6-7".parse::<NaiveDate>(), Ok(d));
assert!("foo".parse::<NaiveDate>().is_err());

// NaiveDateTime 也实现了 FromStr
use chrono::{NaiveDate, NaiveDateTime};
let parse_from_str = NaiveDateTime::parse_from_str;
assert_eq!(
    parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S"),
    Ok(NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap())
);
assert_eq!(
    parse_from_str("5sep2015pm012345.6789", "%d%b%Y%p%I%M%S%.f"),
    Ok(NaiveDate::from_ymd_opt(2015, 9, 5)
        .unwrap()
        .and_hms_micro_opt(13, 23, 45, 678_900)
        .unwrap())
);
// Offset is ignored for the purpose of parsing.
assert_eq!(
    parse_from_str("2014-5-17T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"),
    Ok(NaiveDate::from_ymd_opt(2014, 5, 17).unwrap().and_hms_opt(12, 34, 56).unwrap())
);

格式化:

let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap();
assert_eq!(d.format("%Y-%m-%d").to_string(), "2015-09-05");
assert_eq!(d.format("%A, %-d %B, %C%y").to_string(), "Saturday, 5 September, 2015");

let dt :NaiveDateTime = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap();
assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2015-09-05 23:56:04");
assert_eq!(dt.format("around %l %p on %b %-d").to_string(), "around 11 PM on Sep 5");

NaiveDateTime 实现了 Display trait,显示格式等效于 dt.format("%Y-%m-%d %H:%M:%S%.f") .

use chrono::NaiveDate;

let dt = NaiveDate::from_ymd_opt(2016, 11, 15).unwrap().and_hms_opt(7, 39, 24).unwrap();
assert_eq!(format!("{}", dt), "2016-11-15 07:39:24");

FromStr: NaiveDateTime 的字符串格式 %Y-%m-%dT%H:%M:%S%.f

use chrono::{NaiveDateTime, NaiveDate};

let dt = NaiveDate::from_ymd_opt(2015, 9, 18).unwrap().and_hms_opt(23, 56, 4).unwrap();
assert_eq!("2015-09-18T23:56:04".parse::<NaiveDateTime>(), Ok(dt));

let dt = NaiveDate::from_ymd_opt(12345, 6, 7).unwrap().and_hms_milli_opt(7, 59, 59, 1_500).unwrap(); // leap second
assert_eq!("+12345-6-7T7:59:60.5".parse::<NaiveDateTime>(), Ok(dt));
assert!("foo".parse::<NaiveDateTime>().is_err());

2 TimeZone
#

chrono crate 不包含 Timezone 时区数据,需要使用 chrono-tz crate 或 tzfile crate 来提供完整功能。这两个 trait 都提供了 chrono 的 TimeZone trait 的实现。chrono-tz 自带 timezone 数据库,而 tzfile 使用的是系统的 tz 数据库(/usr/share/zoneinfo)。

use chrono::{TimeZone, Utc, NaiveDate};
use chrono_tz::US::Pacific;
use chrono_tz::Africa::Johannesburg;

let pacific_time = Pacific.ymd(1990, 5, 6).and_hms(12, 30, 45);
let utc_time = pacific_time.with_timezone(&Utc); // 转换为 utc 时区时间
assert_eq!(utc_time, Utc.ymd(1990, 5, 6).and_hms(19, 30, 45));

let naive_dt = NaiveDate::from_ymd(2038, 1, 19).and_hms(3, 14, 08);
let tz_aware = Johannesburg.from_local_datetime(&naive_dt).unwrap();
assert_eq!(tz_aware.to_string(), "2038-01-19 03:14:08 SAST");

TimeZone trait 的方法:

  • 提供了多种创建 DateTime<Tz> 的方法, 如:with_ymd_and_hms() 和 timestamp_XX() 等。
pub trait TimeZone: Sized + Clone {

    type Offset: Offset;

    // Required methods
    fn from_offset(offset: &Self::Offset) -> Self;
    fn offset_from_local_date( &self, local: &NaiveDate ) -> MappedLocalTime<Self::Offset>;
    fn offset_from_local_datetime( &self, local: &NaiveDateTime ) -> MappedLocalTime<Self::Offset>;
    fn offset_from_utc_date(&self, utc: &NaiveDate) -> Self::Offset;
    fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset;

    // Provided methods

    // 从 TimeZone 创建 DateTime,表示已经位于该 TimeZone 的情况下,对应的日期、时间值
    fn with_ymd_and_hms(&self, year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32 ) ->MappedLocalTime<DateTime<Self>> { ... }

    // 创建 Date 对象
    fn ymd(&self, year: i32, month: u32, day: u32) -> Date<Self> { ... }
    fn ymd_opt( &self, year: i32, month: u32, day: u32 ) -> MappedLocalTime<Date<Self>> { ... }
    fn yo(&self, year: i32, ordinal: u32) -> Date<Self> { ... }
    fn yo_opt(&self, year: i32, ordinal: u32) -> MappedLocalTime<Date<Self>> { ... }
    fn isoywd(&self, year: i32, week: u32, weekday: Weekday) -> Date<Self> { ... }
    fn isoywd_opt( &self, year: i32, week: u32, weekday: Weekday ) -> MappedLocalTime<Date<Self>> { ... }
    fn from_local_date(&self, local: &NaiveDate) -> MappedLocalTime<Date<Self>> { ... }
    fn from_utc_date(&self, utc: &NaiveDate) -> Date<Self> { ... }

    // 创建 DateTime 对象,表示已经位于该 TimeZone 的情况下,对应的 secs/nsecs 值。
    fn timestamp(&self, secs: i64, nsecs: u32) -> DateTime<Self> { ... }
    fn timestamp_opt( &self, secs: i64, nsecs: u32 ) -> MappedLocalTime<DateTime<Self>> { ... }
    fn timestamp_millis(&self, millis: i64) -> DateTime<Self> { ... }
    fn timestamp_millis_opt( &self, millis: i64 ) -> MappedLocalTime<DateTime<Self>> { ... }
    fn timestamp_nanos(&self, nanos: i64) -> DateTime<Self> { ... }
    fn timestamp_micros(&self, micros: i64) -> MappedLocalTime<DateTime<Self>> { ... }

    // 从指定格式的字符串解析创建 DateTime 对象,
    fn datetime_from_str( &self, s: &str, fmt: &str ) -> ParseResult<DateTime<Self>> { ... }

    // 从 NaiveDateTime 创建 DateTime 对象
    // 1. 传入的 local 即为 TimeZone 时间,不涉及转换
    fn from_local_datetime( &self, local: &NaiveDateTime ) -> MappedLocalTime<DateTime<Self>> { ... }
    // 2. 将传入的 utc 时间转换为 TimeZone 时间
    fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Self> { ... }
}

chrono 提供了三种常见的 TimeZone 实现:

  1. Utc:UTC time zone;
  2. Local:系统本地的 time zone;
  3. FixedOffset:通过指定任意时区偏移量来自定义时区;

FixedOffset:FixedOffset 返回指定偏移的 TimeZone 对象

pub const fn east_opt(secs: i32) -> Option<FixedOffset>
pub const fn west_opt(secs: i32) -> Option<FixedOffset>
pub const fn local_minus_utc(&self) -> i32
pub const fn utc_minus_local(&self) -> i32

use chrono::{FixedOffset, TimeZone};
let hour = 3600;
let datetime = FixedOffset::east_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap();
assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00")

let offset = FixedOffset::east_opt(5 * 60 * 60).unwrap();
let now_with_offset = Utc::now().with_timezone(&offset); // 将当前 Utc 时间转换为 offset 对应时区时间
println!("now_with_offset: {}", now_with_offset); // now_with_offset: 2024-09-11 17:27:55.505607 +05:00

let tz = FixedOffset::east_opt(5 * hour).unwrap();
let dt = tz.timestamp_opt(1, 1).unwrap(); // 已经位于 FixedOffset 的 timestamp 值
println!("DateTime<FixedOffset>: {}", dt); // DateTime<FixedOffset>: 1970-01-01 05:00:01.000000001 +05:00

let dt = tz.with_ymd_and_hms(1,1,1,1,1,1).unwrap(); // 已经位于 FixedOffset 的日期和时间值
println!("DateTime<FixedOffset> using with_ymd_and_hms {}", dt); // DateTime<FixedOffset> using with_ymd_and_hms 0001-01-01 01:01:01 +05:00

let tz = FixedOffset::east_opt(5 * hour).unwrap();
let ndt = "2024-09-07T20:33:12"
    .parse::<NaiveDateTime>()
    .unwrap();
let dt = offset
    .from_local_datetime(&ndt) // 给 NaiveDateTime 添加 TimeZone 信息,不涉及时间日期转换
    .unwrap();
println!("offset.from_local_datetime: {}", dt); // offset.from_local_datetime: 2024-09-07 20:33:12 +05:00

let tz = FixedOffset::east_opt(5 * hour).unwrap();
let ndt = "2024-09-07T20:33:12"
    .parse::<NaiveDateTime>()
    .unwrap();
let dt = offset
    .from_utc_datetime(&ndt); // 给 NaiveDateTime 添加 TimeZone 信息,将传入的 UTC 时间转换为 TimeZone 时间
println!("offset.from_utc_datetime: {}", dt); // offset.from_utc_datetime: 2024-09-08 01:33:12 +05:00

Utc:Utc 是 unit struct 类型,Utc 是唯一实例,故可以直接调用 TimeZone trait 的方法

pub struct Utc;

use chrono::{DateTime, TimeZone, Utc};
let dt = DateTime::from_timestamp(61, 0).unwrap();

// Utc 是唯一实例,故可以直接调用 TimeZone trait 的方法
assert_eq!(Utc.timestamp_opt(61, 0).unwrap(), dt);

// 传给 with_ymd_and_hms() 的日期时间是已经位于 Utc 的值。
assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap(), dt);

let now_utc = Utc::now(); // pub fn now() -> DateTime<Utc>
let today_utc = now_utc.date_naive();
let offset = FixedOffset::east_opt(5 * 60 * 60).unwrap();
let now_with_offset = Utc::now().with_timezone(&offset);  // 将当前 Utc 时间转换为 offset 对应时区时间

Local:Local 和 Utc 类似,也是 unit struct 类型;

pub struct Local;

use chrono::{DateTime, Local, TimeZone};
let dt1: DateTime<Local> = Local::now(); // pub fn now() -> DateTime<Local>
// Local 是唯一示例,故可以直接调用 TimeZone trait 的方法
let dt2: DateTime<Local> = Local.timestamp_opt(0, 0).unwrap();
assert!(dt1 >= dt2);

从 timestamp 创建 DateTime:

  1. DateTime 的 from_timestamp() 方法;等效于 Utc.timestamp_opt()
  2. TimeZone 的 timestamp_opt() 方法,如 Utc 或 Local 的 timestamp_opt();
let dt = DateTime::from_timestamp(61, 0).unwrap();
assert_eq!(Utc.timestamp_opt(61, 0).unwrap(), dt);

let dt2: DateTime<Local> = Local.timestamp_opt(0, 0).unwrap();

例子:

use chrono::prelude::*;

fn main() {
    // 返回不带时区信息的 NaiveDate/NaiveTime/NaiveDateTime 对象
    let date = NaiveDate::from_ymd(2023, 4, 16);
    let time = NaiveTime::from_hms(12, 34, 56);
    let datetime = NaiveDateTime::new(date, time);

    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`
    let dt = Utc.with_ymd_and_hms(2014, 7, 8, 9, 10, 11).unwrap(); // `2014-07-08T09:10:11Z`

    let offset = FixedOffset::east_opt(5 * 60 * 60).unwrap();
    let now_with_offset = Utc::now().with_timezone(&offset); //转换为 offset 时区时间

    NaiveDate::from_ymd_opt(2014, 7, 8)?
        .and_hms_opt(9, 10, 11)?
        .and_utc() // 为 NaiveDate 关联 Utc,返回 DateTime<Utc>

    println!("Date: {}", date);
    println!("Time: {}", time);
    println!("DateTime: {}", datetime);
    println!("UTC DateTime: {}", utc_datetime);
}

3 DateTime
#

DateTime 对象有一个泛型参数 Tz,类型是 TimeZone trait:

pub struct DateTime<Tz: TimeZone> { /* private fields */ }

impl<Tz: TimeZone> DateTime<Tz>

// 从 naive utc 和 offset 创建一个 DateTime
pub const fn from_naive_utc_and_offset(
    datetime: NaiveDateTime, // UTC Date and Time,可以从已有的 DateTime.naive_utc() 获得。
    offset: Tz::Offset // TimeZone offset, 可以从已有的 DateTime.offset() 获得。
) -> DateTime<Tz>

use chrono::{DateTime, Local};
let dt = Local::now();
let naive_utc = dt.naive_utc();
let offset = dt.offset().clone();
let dt_new = DateTime::<Local>::from_naive_utc_and_offset(naive_utc, offset);
assert_eq!(dt, dt_new);

// 创建 DateTime<Utc> 类型的关联函数(构造函数)
impl DateTime<Utc>
pub const fn from_timestamp(secs: i64, nsecs: u32) -> Option<Self>
pub const fn from_timestamp_millis(millis: i64) -> Option<Self>
pub const fn from_timestamp_micros(micros: i64) -> Option<Self>
pub const fn from_timestamp_nanos(nanos: i64) -> Self

// 解析字符串,创建 DateTime<FixedOffset>类型的关联函数(构造函数)
impl DateTime<FixedOffset>
pub fn parse_from_rfc2822(s: &str) -> ParseResult<DateTime<FixedOffset>>
pub fn parse_from_rfc3339(s: &str) -> ParseResult<DateTime<FixedOffset>>
pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult<DateTime<FixedOffset>>
pub fn parse_and_remainder<'a>( s: &'a str, fmt: &str ) -> ParseResult<(DateTime<FixedOffset>, &'a str)>

// 返回 Utc 或指定 TimeZone 的 DateTime
pub fn fixed_offset(&self) -> DateTime<FixedOffset>
pub const fn to_utc(&self) -> DateTime<Utc>
pub fn with_timezone<Tz2: TimeZone>(&self, tz: &Tz2) -> DateTime<Tz2>

pub fn date_naive(&self) -> NaiveDate
pub const fn naive_utc(&self) -> NaiveDateTime
pub fn naive_local(&self) -> NaiveDateTime
pub fn time(&self) -> NaiveTime

pub const fn timestamp(&self) -> i64
pub const fn timestamp_millis(&self) -> i64
pub const fn timestamp_micros(&self) -> i64
pub const fn timestamp_nanos_opt(&self) -> Option<i64>
pub const fn timestamp_subsec_millis(&self) -> u32
pub const fn timestamp_subsec_micros(&self) -> u32
pub const fn timestamp_subsec_nanos(&self) -> u32

// 返回关联的 TimeZone 或 Offset
pub const fn offset(&self) -> &Tz::Offset
pub fn timezone(&self) -> Tz

pub fn checked_add_signed(self, rhs: TimeDelta) -> Option<DateTime<Tz>>
pub fn checked_add_months(self, months: Months) -> Option<DateTime<Tz>>
pub fn checked_sub_signed(self, rhs: TimeDelta) -> Option<DateTime<Tz>>
pub fn checked_sub_months(self, months: Months) -> Option<DateTime<Tz>>
pub fn checked_add_days(self, days: Days) -> Option<Self>
pub fn checked_sub_days(self, days: Days) -> Option<Self>

// 返回两个 DateTime 的时间差
pub fn signed_duration_since<Tz2: TimeZone>( self, rhs: impl Borrow<DateTime<Tz2>> ) -> TimeDelta
pub fn years_since(&self, base: Self) -> Option<u32>

// 格式化显示
pub fn to_rfc2822(&self) -> String
pub fn to_rfc3339(&self) -> String
pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String

// 设置 time 部分,如果 time 不对则返回 LocalResult::None
pub fn with_time(&self, time: NaiveTime) -> LocalResult<Self>

impl<Tz: TimeZone> DateTime<Tz> where Tz::Offset: Display
pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat<I>
    where
    I: Iterator<Item = B> + Clone,
    B: Borrow<Item<'a>>

pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat<StrftimeItems<'a>>

pub fn format_localized_with_items<'a, I, B>(
    &self,
    items: I,
    locale: Locale
    ) -> DelayedFormat<I>
    where
    I: Iterator<Item = B> + Clone,
    B: Borrow<Item<'a>>

pub fn format_localized<'a>(
    &self,
    fmt: &'a str,
    locale: Locale
    ) -> DelayedFormat<StrftimeItems<'a>>


// DateTIme 实现了如下 Add/AddAssign/Sub/SubAssgn trait
impl<Tz: TimeZone> Add<Days> for DateTime<Tz>
impl<Tz: TimeZone> Add<Duration> for DateTime<Tz>
impl<Tz: TimeZone> Add<FixedOffset> for DateTime<Tz>
impl<Tz: TimeZone> Add<Months> for DateTime<Tz>
impl<Tz: TimeZone> Add<TimeDelta> for DateTime<Tz>
impl<Tz: TimeZone> AddAssign<Duration> for DateTime<Tz> // 标准度 std::time::Duration
impl<Tz: TimeZone> AddAssign<TimeDelta> for DateTime<Tz>

使用 DateTime 对象:

  • 各种 from_XX() 方法可以创建 DateTime 对象;
  • 各种 with_XX() 方法可以修改 DateTime 对象;
  • DateTime 可以和 TimeDelta 进行算术运算;
  • 各方法中的 yo 和 ordinal 表示一年中的天数;
use chrono::prelude::*;
use chrono::TimeDelta;

// assume this returned `2014-11-28T21:45:59.324310806+09:00`:
let dt = FixedOffset::east_opt(9 * 3600)
    .unwrap()
    .from_local_datetime(
        &NaiveDate::from_ymd_opt(2014, 11, 28)
            .unwrap()
            .and_hms_nano_opt(21, 45, 59, 324310806)
            .unwrap(),
    )
    .unwrap();

// 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_opt(9 * 3600).unwrap());
assert_eq!(
    dt.with_timezone(&Utc), // 转换为 Utc 时区
    NaiveDate::from_ymd_opt(2014, 11, 28)
    .unwrap()
    .and_hms_nano_opt(12, 45, 59, 324310806)
    .unwrap()
    .and_utc()
);


// 各种 with_XX() 方法可以修改 DateTime
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.with_ymd_and_hms(2014, 11, 14, 8, 9, 10).unwrap();
let dt2 = Utc.with_ymd_and_hms(2014, 11, 14, 10, 9, 8).unwrap();

DateTime 和 EPOCH timestamp 间的相互转换:

  • 使用 DateTime::from_timestamp(seconds,nanosecdonds) 来从 timestamp 创建 DateTime<Utc>;
  • 使用 DateTime::timestamp() 来返回 Unix timestamp;
// We need the trait in scope to use Utc::timestamp().
use chrono::{DateTime, Utc};

// Construct a datetime from epoch:
let dt: DateTime<Utc> = DateTime::from_timestamp(1_500_000_000, 0).unwrap();
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);

日期、时间的算术运算和比较

use chrono::{NaiveDate, Duration};

fn main() {
    let date1 = NaiveDate::from_ymd(2023, 4, 16);
    let date2 = NaiveDate::from_ymd(2023, 5, 1);

    // Adding and subtracting durations
    let date_plus_one_week = date1 + Duration::days(7);
    let date_minus_one_month = date1 - Duration::days(30);
    println!("One week later: {}", date_plus_one_week);
    println!("One month earlier: {}", date_minus_one_month);

    // Comparing dates
    if date1 < date2 {
        println!("{} is earlier than {}", date1, date2);
    }

    // Calculating the difference between dates
    let duration = date2 - date1;
    println!("There are {} days between {} and {}", duration.num_days(), date1, date2);
}

chrono 提供了 TimeDelta 及其别名 Duration 类型,和标准库的 std::time::Durataion 的主要区别是: TimeDelta 是有符号类型,而非无符号类型。

  • 两者可以相互转换:TimeDelta::from_std() 和 TimeDelta::to_std();

4 TimeDelta
#

TimeDelta 是纳秒精度的时间间隔,内部记录 seconds 和 nanoseconds,别名类型为 Duration;

  • 和 std::time::Duration 相比,TimeDelta 是有符号的,可以是负值。
pub struct TimeDelta { /* private fields */ }

// TimeDelta 方法:
pub const fn new(secs: i64, nanos: u32) -> Option<TimeDelta>

pub const fn weeks(weeks: i64) -> TimeDelta
pub const fn try_weeks(weeks: i64) -> Option<TimeDelta>

pub const fn days(days: i64) -> TimeDelta
pub const fn try_days(days: i64) -> Option<TimeDelta>

pub const fn hours(hours: i64) -> TimeDelta
pub const fn try_hours(hours: i64) -> Option<TimeDelta>

pub const fn minutes(minutes: i64) -> TimeDelta
pub const fn try_minutes(minutes: i64) -> Option<TimeDelta>

pub const fn seconds(seconds: i64) -> TimeDelta
pub const fn try_seconds(seconds: i64) -> Option<TimeDelta>

pub const fn milliseconds(milliseconds: i64) -> TimeDelta
pub const fn try_milliseconds(milliseconds: i64) -> Option<TimeDelta>
pub const fn microseconds(microseconds: i64) -> TimeDelta
pub const fn nanoseconds(nanos: i64) -> TimeDelta

pub const fn num_weeks(&self) -> i64
pub const fn num_days(&self) -> i64
pub const fn num_hours(&self) -> i64
pub const fn num_minutes(&self) -> i64
pub const fn num_seconds(&self) -> i64
pub const fn subsec_nanos(&self) -> i32
pub const fn num_milliseconds(&self) -> i64
pub const fn num_microseconds(&self) -> Option<i64>
pub const fn num_nanoseconds(&self) -> Option<i64>

pub const fn checked_add(&self, rhs: &TimeDelta) -> Option<TimeDelta>
pub const fn checked_sub(&self, rhs: &TimeDelta) -> Option<TimeDelta>
pub const fn checked_mul(&self, rhs: i32) -> Option<TimeDelta>
pub const fn checked_div(&self, rhs: i32) -> Option<TimeDelta>
pub const fn abs(&self) -> TimeDelta
pub const fn min_value() -> TimeDelta
pub const fn max_value() -> TimeDelta
pub const fn zero() -> TimeDelta
pub const fn is_zero(&self) -> bool

pub const fn from_std(duration: Duration) -> Result<TimeDelta, OutOfRangeError>
pub const fn to_std(&self) -> Result<Duration, OutOfRangeError>

大多数 chrono 类型实现了 std::ops::Add trait, 可以和 TimeDelta 相加:

impl<Tz: TimeZone> Add<TimeDelta> for Date<Tz>
impl<Tz: TimeZone> Add<TimeDelta> for DateTime<Tz>
impl Add<TimeDelta> for NaiveDate
impl Add<TimeDelta> for NaiveDateTime
impl Add<TimeDelta> for NaiveTime
impl Add for TimeDelta

impl<Tz: TimeZone> AddAssign<TimeDelta> for Date<Tz>
impl<Tz: TimeZone> AddAssign<TimeDelta> for DateTime<Tz>
impl AddAssign<TimeDelta> for NaiveDate
impl AddAssign<TimeDelta> for NaiveDateTime
impl AddAssign<TimeDelta> for NaiveTime
impl AddAssign for TimeDelta

5 解析和格式化
#

解析和格式化 Date 和 Time 对象:

  • parse() : 可以用于从字符串创建 DateTime<FixedOffset>, DateTime<Utc> 和 DateTime<Local> 值;
  • parse_from_str() : 来创建 NaiveDate/DateTime<FixedOffset> 对象,字符串必须包含 offset,否则不能 guess;
  • DateTime::parse_from_rfc2822 和 DateTime::parse_from_rfc3339:用于解析特定的格式字符串;
  • format() 将 NaiveDate/DateTime 对象格式化为字符串;
  • parse 和 format 的格式字符串语法参考 chrono::format::strftime module
use chrono::{NaiveDate, NaiveDateTime, DateTime, Utc, LocalResult};

// Parsing a date from a string
let date_str = "2023-04-16";
let parsed_date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d").unwrap();
println!("Parsed Date: {}", parsed_date);

// Parsing a datetime from a string
let datetime_str = "2023-04-16T12:34:56Z";
let parsed_datetime = DateTime::parse_from_rfc3339(datetime_str).unwrap();
println!("Parsed DateTime: {}", parsed_datetime);

// Formatting a date as a string
let formatted_date = parsed_date.format("%A, %B %e, %Y");
println!("Formatted Date: {}", formatted_date);

// Formatting a datetime as a string
let formatted_datetime = parsed_datetime.format("%Y-%m-%dT%H:%M:%S%z");
println!("Formatted DateTime: {}", formatted_datetime);

let dt = Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap();
let fixed_dt = dt.with_timezone(&FixedOffset::east_opt(9 * 3600).unwrap());

// 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()));

// oops, the year is missing!
assert!(DateTime::parse_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!(DateTime::parse_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err());
// oops, the weekday is incorrect!
assert!(DateTime::parse_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err());


let dt = Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap();
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_localized("%A %e %B %Y, %T", Locale::fr_BE).to_string(),
    "vendredi 28 novembre 2014, 12:00:09"
);

assert_eq!(dt.format("%a %b %e %T %Y").to_string(), dt.format("%c").to_string());

// 缺省的 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");

// Debug 实现
assert_eq!(format!("{:?}", dt), "2014-11-28T12:00:09Z");

// 仅在 milli/nanoseconds 非 0 时才打印
let dt_nano = NaiveDate::from_ymd_opt(2014, 11, 28)
    .unwrap()
    .and_hms_nano_opt(12, 0, 9, 1)
    .unwrap()
    .and_utc();
assert_eq!(format!("{:?}", dt_nano), "2014-11-28T12:00:09.000000001Z");

6 chrono 集成
#

diesel 等 ORM 引擎提供了 chrono 时间集成的能力: https://docs.rs/diesel/latest/diesel/sql_types/struct.Datetime.html

chrono::serde module 提供了 serde crate 集成的能力(基于 serde with attribute):

  • #[serde(with = “module”)] : Combination of serialize_with and deserialize_with. Serde will use $module::serialize as the serialize_with function and $module::deserialize as the deserialize_with function.
use chrono::serde::ts_microseconds;

#[derive(Deserialize, Serialize)]
struct S {
    #[serde(with = "ts_microseconds")]
    time: DateTime<Utc>
}

let time = NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_local_timezone(Utc).unwrap();
let my_s = S {
    time: time.clone(),
};

let as_string = serde_json::to_string(&my_s)?;
assert_eq!(as_string, r#"{"time":1526522699918355}"#);
let my_s: S = serde_json::from_str(&as_string)?;
assert_eq!(my_s.time, time);

use chrono::serde::ts_microseconds_option;
#[derive(Deserialize, Serialize)]
struct S {
    #[serde(with = "ts_microseconds_option")]
    time: Option<DateTime<Utc>>
}

let time = Some(NaiveDate::from_ymd_opt(2018, 5, 17).unwrap().and_hms_micro_opt(02, 04, 59, 918355).unwrap().and_local_timezone(Utc).unwrap());
let my_s = S {
    time: time.clone(),
};

let as_string = serde_json::to_string(&my_s)?;
assert_eq!(as_string, r#"{"time":1526522699918355}"#);
let my_s: S = serde_json::from_str(&as_string)?;
assert_eq!(my_s.time, time);
rust crate - 这篇文章属于一个选集。
§ 3: 本文

相关文章

anyhow
··1872 字
Rust Rust-Crate
anyhow crate 提供了自定义 Error 类型和 Result 类型,Error 类型自带 backtrace 和 context,支持用户友好的格式化信息输出。
bytes
··2900 字
Rust Rust-Crate
bytes 提供了高效的 zero-copy 连续内存区域的共享和读写能力。
hyper
··861 字
Rust Rust-Crate
hyper 是高性能的异步 HTTP 1/2 底层库。
serde
··7295 字
Rust Rust-Crate
Rust 主流的序列化/反序列化库。