Rustで日時を扱う

この記事は[Wanoアドベントカレンダー](https://qiita.com/advent-calendar/2018/wano-group)の7日目の記事です。

## TL;DR

* 日時を扱う時は[chrono](https://crates.io/crates/chrono)を使う
* timezoneをがっつり扱う時は[chrono-tz](https://crates.io/crates/chrono-tz)が便利

## 日時を扱う

Rustで日時を扱う時は[chrono](https://crates.io/crates/chrono)がデファクトなのでこれを使います。
様々なタイムゾーンを扱う場合は[chrono-tz](https://crates.io/crates/chrono-tz)を合わせて使うと便利です。

## chronoが提供している型

主な日時に関する型は次の通りです。

* [DateTime<Tz>](https://docs.rs/chrono/0.4.6/chrono/struct.DateTime.html)
* [Date<Tz>](https://docs.rs/chrono/0.4.6/chrono/struct.Date.html)
* [NaiveDateTime](https://docs.rs/chrono/0.4.6/chrono/naive/struct.NaiveDateTime.html)
* [NaiveDate](https://docs.rs/chrono/0.4.6/chrono/naive/struct.NaiveDate.html)
* [NaiveTime](https://docs.rs/chrono/0.4.6/chrono/naive/struct.NaiveTime.html)

`DateTime<Tz>`と`Date<Tz>`はタイムゾーン情報を持ちます。
`Naive`が頭に付いている型はタイムゾーン情報を持ちません。

また、タイムゾーン情報は型パラメータとして次の様に持ちます。

| 型                      | 意味                         |
| ----------------------- | ---------------------------- |
| `DateTime<Utc>`         | UTCの日時型                  |
| `Date<Utc>`             | UTCの日付型                  |
| `DateTime<Local>`       | ローカルタイムゾーンの日時型 |
| `Date<Local>`           | ローカルタイムゾーンの日付型 |
| `DateTime<FixedOffset>` | UTCからN時間ずれた日時型     |
| `Date<FixedOffset>`     | UTCからN時間ずれた日付型     |

[FixedOffset](https://docs.rs/chrono/0.4.6/chrono/offset/struct.FixedOffset.html)は[Utc](https://docs.rs/chrono/0.4.6/chrono/offset/struct.Utc.html)と[Local](https://docs.rs/chrono/0.4.6/chrono/offset/struct.Local.html)以外を扱う時に使用します(例えば+03:00にしたい時)。

その他に複数の日時に関する型を抽象化して扱いたい時の為に、以下のtraitが提供されています。

* [Datelike](https://docs.rs/chrono/0.4.6/chrono/trait.Datelike.html)
* [Timelike](https://docs.rs/chrono/0.4.6/chrono/trait.Timelike.html)

## よくある処理のやり方

### 現在時刻の取得

`Utc`や`Local`の`now`関数や`today`関数を使って取得します。

~~~~rust
use chrono::{Utc, Local, DateTime, Date};

fn main() {
    let utc_datetime: DateTime<Utc> = Utc::now();
    let utc_date: Date<Utc> = Utc::today();

    println!("{}", utc_datetime);
    println!("{}", utc_date);

    let local_datetime: DateTime<Local> = Local::now();
    let local_date: Date<Local> = Local::today();

    println!("{}", local_datetime);
    println!("{}", local_date);
}
~~~~

実行結果:

~~~~text
2018-12-07 10:27:26.682066134 UTC
2018-12-07UTC
2018-12-07 19:27:26.682094317 +09:00
2018-12-07+09:00
~~~~

### 日時文字列のパース

`DateTime`や`NaiveDateTime`などの`parse_from_str`や`parse_from_rfc3339`などの関数を使います。

~~~~rust
use chrono::{DateTime, Utc, Local, offset::{FixedOffset, TimeZone}, NaiveDateTime};

fn main() {
    let dt: Result<DateTime<FixedOffset>, _> = DateTime::parse_from_rfc3339("2018-12-07T19:31:28+09:00");
    println!("DateTime::parse_from_rfc3339: {:?}", dt);

    let dt: Result<DateTime<FixedOffset>, _> = DateTime::parse_from_str("2018/12/07 19:31:28 +0900", "%Y/%m/%d %H:%M:%S %z");
    println!("DateTime::parse_from_str: {:?}", dt);
    
    let dt: Result<DateTime<Utc>, _> = Utc.datetime_from_str("2018/12/07 19:31:28", "%Y/%m/%d %H:%M:%S");
    println!("Local.datetime_from_str: {:?}", dt);

    let dt: Result<DateTime<Local>, _> = Local.datetime_from_str("2018/12/07 19:31:28", "%Y/%m/%d %H:%M:%S");
    println!("Local.datetime_from_str: {:?}", dt);

    let dt: Result<NaiveDateTime, _> = NaiveDateTime::parse_from_str("2018/12/07 19:31:28", "%Y/%m/%d %H:%M:%S");
    println!("NaiveDateTime::parse_from_str: {:?}", dt);
}
~~~~

実行結果:

~~~~text
DateTime::parse_from_rfc3339: Ok(2018-12-07T19:31:28+09:00)
DateTime::parse_from_str: Err(ParseError(NotEnough))
NaiveDateTime::parse_from_str: Ok(2018-12-07T19:31:28)
~~~~

### 文字列に変換

`format`メソッドを使います。
書式指定はよくあるやり方です（詳しくは [chrono::format::strftime](https://docs.rs/chrono/0.4.6/chrono/format/strftime/index.html) に書かれています）。

~~~~rust
use chrono::Utc;

fn main() {
    let text = Utc::now().format("%Y年%m月%d日 %H時%M分%S秒 %Z").to_string();
    println!("{}", text);
}
~~~~

実行結果:

~~~~text
2018年12月07日 10時43分54秒 UTC
~~~~

### TimeZone有りの型から無しの型への変換、またはその逆

`DateTime<Tz>`から`NaiveDateTime`へ変換する場合は、`naive_utc`や`naive_local`メソッドを使います。
`naive_local`は`Tz`での時刻で`NaiveDateTime`を生成し、`naive_utc`は`Utc`での時刻で`NaiveDateTime`を生成します。

~~~~rust
use chrono::{Utc, Local, DateTime, NaiveDateTime};

fn main() {
    let utc: DateTime<Utc>= Utc::now();
    println!("[DateTime<Utc> -> NaiveDateTime] utc:         {:?}", utc);

    let naive: NaiveDateTime = utc.naive_local();
    println!("[DateTime<Utc> -> NaiveDateTime] naive_local: {:?}", naive);

    let naive: NaiveDateTime = utc.naive_utc();
    println!("[DateTime<Utc> -> NaiveDateTime] naive_utc:   {:?}", naive);

    let local: DateTime<Local> = Local::now();
    println!("[DateTime<Local> -> NaiveDateTime] local:       {:?}", local);

    let naive: NaiveDateTime = local.naive_local();
    println!("[DateTime<Local> -> NaiveDateTime] naive_local: {:?}", naive);

    let naive: NaiveDateTime = local.naive_utc();
    println!("[DateTime<Local> -> NaiveDateTime] naive_utc:   {:?}", naive);
}
~~~~

実行結果:

~~~~text
[DateTime<Utc> -> NaiveDateTime] utc:         2018-12-07T10:53:33.933340068Z
[DateTime<Utc> -> NaiveDateTime] naive_local: 2018-12-07T10:53:33.933340068
[DateTime<Utc> -> NaiveDateTime] naive_utc:   2018-12-07T10:53:33.933340068
[DateTime<Local> -> NaiveDateTime] local:       2018-12-07T19:53:33.933364073+09:00
[DateTime<Local> -> NaiveDateTime] naive_local: 2018-12-07T19:53:33.933364073
[DateTime<Local> -> NaiveDateTime] naive_utc:   2018-12-07T10:53:33.933364073
~~~~

上記の逆に、`NaiveDateTime`から`DateTime<Tz>`へ変換する場合は、`TimeZone` traitの`from_local_datetime`や`from_utc_datetime`メソッドを使います。

~~~~rust
use chrono::{Utc, Local, FixedOffset, DateTime, NaiveDateTime, TimeZone};

fn main() {
    let dt: NaiveDateTime = NaiveDateTime::parse_from_str("2018/12/07 19:31:28", "%Y/%m/%d %H:%M:%S").unwrap();

    let utc: DateTime<Utc> = Utc.from_local_datetime(&dt).unwrap();
    println!("{}", utc);

    let local: DateTime<Local> = Local.from_local_datetime(&dt).unwrap();
    println!("{}", local);

    let offset: DateTime<FixedOffset> = FixedOffset::east(3 * 3600).from_local_datetime(&dt).unwrap();
    println!("{}", offset);
}
~~~~

実行結果:

~~~~text
2018-12-07 19:31:28 UTC
2018-12-07 19:31:28 +09:00
2018-12-07 19:31:28 +03:00
~~~~

### 2つの日時の差を求める

単純に引き算で計算できます。
計算した結果は[chrono::Duration](https://docs.rs/chrono/0.4.6/chrono/struct.Duration.html)型になります。

~~~~rust
use chrono::{NaiveDateTime, Duration};

fn main() {
    let dt1: NaiveDateTime = NaiveDateTime::parse_from_str("2018/12/07 19:31:28", "%Y/%m/%d %H:%M:%S").unwrap();
    let dt2: NaiveDateTime = NaiveDateTime::parse_from_str("2018/12/07 12:31:28", "%Y/%m/%d %H:%M:%S").unwrap();
    let duration: Duration = dt1 - dt2;

    println!("secs: {}", duration.num_seconds());
    println!("minutes: {}", duration.num_minutes());
    println!("hours: {}", duration.num_hours());
}
~~~~

実行結果:

~~~~text
secs: 25200
minutes: 420
hours: 7
~~~~

### 日時に時間を足す・引く

`DateTime`や`NaiveDateTime`に`Duration`を足したり引いたりすることで出来ます。

~~~~rust
use chrono::{DateTime, Local, TimeZone, Duration};

fn main() {
    let dt1: DateTime<Local> = Local.datetime_from_str("2018/12/07 19:31:28", "%Y/%m/%d %H:%M:%S").unwrap();
    let dt2 = dt1 + Duration::hours(3);
    let dt3 = dt1 - Duration::hours(3);

    println!("dt1: {}", dt1);
    println!("dt2: {}", dt2);
    println!("dt3: {}", dt3);
}
~~~~

実行結果:

~~~~text
dt1: 2018-12-07 19:31:28 +09:00
dt2: 2018-12-07 22:31:28 +09:00
dt3: 2018-12-07 16:31:28 +09:00
~~~~

### TimeZoneの変更

`with_timezone`メソッドを使います。

~~~~rust
use chrono::{DateTime, Local, Utc, FixedOffset, TimeZone, Duration};

fn main() {
    let dt1: DateTime<Local> = Local.datetime_from_str("2018/12/07 19:31:28", "%Y/%m/%d %H:%M:%S").unwrap();
    let dt2: DateTime<Utc> = dt1.with_timezone(&Utc);
    let dt3: DateTime<FixedOffset> = dt1.with_timezone(&FixedOffset::east(3*3600));

    println!("dt1: {}", dt1);
    println!("dt2: {}", dt2);
    println!("dt3: {}", dt3);
}
~~~~

実行結果:

~~~~text
dt1: 2018-12-07 19:31:28 +09:00
dt2: 2018-12-07 10:31:28 UTC
dt3: 2018-12-07 13:31:28 +03:00
~~~~

### unix timeからの変換

`TimeZone` traitの`timestamp`メソッドを使います。

~~~~rust
use chrono::{DateTime, Local, Utc, FixedOffset, TimeZone};

fn main() {
    let dt1: DateTime<Local> = Local.timestamp(1_500_000_000, 0);
    let dt2: DateTime<Utc> = Utc.timestamp(1_500_000_000, 0);
    let dt3: DateTime<FixedOffset> = FixedOffset::east(3*3600).timestamp(1_500_000_000, 0);

    println!("dt1: {}", dt1);
    println!("dt2: {}", dt2);
    println!("dt3: {}", dt3);
}
~~~~

実行結果:

~~~~text
dt1: 2017-07-14 11:40:00 +09:00
dt2: 2017-07-14 02:40:00 UTC
dt3: 2017-07-14 05:40:00 +03:00
~~~~

### unix timeへの変換

`DateTime`などの`timestamp`メソッドを使います。

~~~~rust
use chrono::{DateTime, Local};

fn main() {
    let dt: DateTime<Local> = Local::now();
    let timestamp: i64 = dt.timestamp();

    println!("{}", timestamp);
}
~~~~

実行結果:

~~~~text
1544182094
~~~~

## chronoをserdeでSerialize/Deserialize出来るようにする

chronoはデフォルトではserdeに対応していません (serdeを使わずにchronoだけ使う場合もある為)。
serdeでserialize/deserializeできるようにしたい場合はCargo.tomlの[dependencies]に次のように書きます。

~~~~toml
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
~~~~

## chronoをdieselで使えるようにする

Rustの代表的ORMのdieselもデフォルトではchronoに対応していません。
serdeの場合と同様に対応する事で使えるようになりますが、diesel側に設定を行います。

~~~~toml
[dependencies]
chrono = "0.4"
diesel = { version = "1.3", features = ["chrono"] }
~~~~