Rust向け日付・時刻ライブラリ Jiff
(github.com/BurntSushi)- JiffはRust向けの日付・時刻ライブラリで、誤用しにくい高水準の日付・時刻プリミティブ型と、妥当な性能を目標としている
- 中核機能は、Time Zone Databaseとの自動連携、DSTを認識した算術演算と丸め、タイムゾーン付き日付・時刻の損失なしフォーマット・パース、オプションのSerdeサポート
- API設計は、JavaScriptの日付・時刻処理を改善するためのTC39提案であるTemporalから大きな影響を受けている
- Jiff 1.0は当初2025年夏のリリースを目標としていたが、2026年4月時点で予定は後ろ倒しとなっており、現在1.0のリリース時期は未定
- 性能は重要な目標ではあるが、APIの理解しやすさと正確性を優先しており、Unixでは意図的に依存関係ゼロを維持する保守的な依存関係ポリシーを採っている
Jiffが提供する日付・時刻モデル
- JiffはRustで日付・時刻を扱うためのライブラリで、誤って使いにくい高水準の基本型を提供することに重点を置いている
- 主な対応機能
- Time Zone Databaseとの自動かつシームレスな統合
- DSTを認識する算術演算と丸め
- タイムゾーン付き日付・時刻の損失なしフォーマット・パース
- オプションの
serdeサポート
- API設計は、JavaScriptの日付・時刻処理を改善するためのTC39提案であるTemporalから大きな影響を受けている
- ライセンスはMITまたはUNLICENSEのデュアルライセンス
ドキュメントと使用例
- ドキュメントと関連資料
- docs.rsのAPIドキュメント
chrono、time、hifitime、icuとの比較ドキュメント- Jiff API設計の根拠ドキュメント
- プラットフォームサポート文書
- 変更ログ
- 基本例では、RFC 3339の時刻文字列をパースしてからタイムゾーン付き日付・時刻に変換し、期間を加えたあと損失なし文字列として出力する
"2024-07-11T01:14:00Z"をTimestampとしてパースAmerica/New_Yorkタイムゾーンへ変換1.month().hours(2)を加算- 結果の文字列は
"2024-08-10T23:14:00-04:00[America/New_York]" - RFC 3339形式のUTC時刻は
"2024-08-11T03:14:00Z"
インストールと基本的な実行フロー
- Jiffはcrates.ioで公開されており、Rustプロジェクトの
Cargo.tomlの依存関係にjiffを追加して利用できる - 簡単な追加コマンドは
cargo add jiff - READMEの完全なサンプル手順
cargo new jiff-exampleで新しいプロジェクトを作成cargo add jiffで依存関係を追加src/main.rsでZoned::now().round(Unit::Second)?により現在時刻を秒単位に丸めるcargo runで実行
- サンプル出力では、タイムゾーンを含む形式の
2024-07-10T19:54:20-04:00[America/New_York]が表示される - 初回実行ではコンパイル出力が多くなるが、その後の実行では依存関係を再コンパイルする必要はない
機能フラグと1.0計画
- Jiffは、Rust標準ライブラリ対応、
serdeサポート、Time Zone Databaseをバイナリへ埋め込むかどうかを調整する複数のcrate featureを提供している - 利用可能な機能フラグの一覧は、ドキュメントの「crate features」セクションにある
- Jiff 1.0の当初のリリース計画は2025年夏だったが、2026年4月時点で予定はすでに後ろ倒しとなっており、現在のリリース時期は未定
- 1.0リリース後はAPIを無期限に維持する計画であり、ユーザーが公開APIの安定した基盤としてJiffを使えるようにすることが目標
- Jiff 1.0リリース後も、Jiff 0.2は1年間にわたって重要なバグ修正アップデートを受ける予定
- アクティブな機能開発は行わない
- すでにJiff 0.2を利用しているユーザーが多いため、移行猶予期間を設ける方針
性能と高度なフォーマット経路
- 最も重要な設計目標は、誤用しにくい高水準の日付・時刻ライブラリになること
- 性能は第2の目標であり、妥当な性能を目指しているが、改善の余地がある領域もありうる
- ベンチマークは
benchディレクトリにある - 一部の領域は最適化されているが、まだ最適化されていない領域も多い
- 性能改善は目標ではあるが、一般にAPIの理解しやすさと正確性が性能より優先される
- Jiffの下位モジュール、特に
jiff::fmtは、より細かな制御が必要な複雑なAPIへの抜け道として機能するZonedのstd::fmt::Display実装は、再利用可能なバッファへの書き込みを難しくする場合があるjiff::fmt::temporal::DateTimePrinter::print_zonedを使うと、Zonedを既存のStringやVec<u8>に書き込める
プラットフォーム別のタイムゾーン処理
- 日付・時刻ライブラリにおけるプラットフォームサポートの問題は、主にタイムゾーン対応に関係している
- Jiffが扱うプラットフォームとの相互作用
Antarctica/TrollのようなIANAタイムゾーン識別子の遷移データを見つける方法- 現在のシステムのデフォルトタイムゾーンを見つける方法
- タイムゾーン遷移データはIANA Time Zone Databaseに依存している
- Unixでは通常
/usr/share/zoneinfoを使用し、TZDIR環境変数の設定にも従う - Windowsでは、コンパイル済みライブラリにTime Zone Databaseのコピーを自動的に埋め込む
- システムタイムゾーンの検出方法
- Unixでは
/etc/localtimeを読む - Windowsでは
GetDynamicTimeZoneInformationでWindows専用のタイムゾーン識別子を取得し、UnicodeのCLDR XML dataを通じてIANAタイムゾーン識別子へマッピングする
- Unixでは
- 他プラットフォームの対応は今後時間とともに増える見込みだが、テストが難しい珍しいプラットフォームについては、コントリビューターからのpull requestに依存する可能性が高い
依存関係とMSRVポリシー
- JiffがUnixで依存関係ゼロなのは意図的な設計
- エコシステムcrateであるJiffに新たな依存関係を追加する基準は保守的
- プラットフォームと相互作用するために実質的に必要な場合
- 互換運用に必要な場合。たとえば
serde
- 互換運用目的の依存関係も保守的に扱い、一般にJiffよりbreaking changeリリースが少ないものにのみ依存しようとしている
- Jiffを複数のcrateに分割する場合は新たな依存関係が生じる可能性があるが、現時点ではその計画はない
- crate数は小さく保つ方針であり、多数のsemver API境界を管理する保守オーバーヘッドが大きいという経験が示されている
- Jiffの最小サポートRustコンパイラバージョンはMSRV 1.70.0
- Jiff本体およびリポジトリ内のJiff依存関係は、直近1年以内にリリースされたRustコンパイラを要求するRustバージョンを使用しない
jiff-icuのようにリポジトリ内でJiffに依存するcrateは、より新しいMSRVを使うことがあるが、統合対象のcrateより新しいMSRVを要求することはないwindows-sys、portable-atomicのような外部依存関係のMSRVはJiffでは制御できない- Rustエコシステムの慣例に従い、JiffはMSRV引き上げをsemver非互換変更とは見なさないが、MSRVの引き上げはminor versionアップデートに限定する
1件のコメント
Hacker News の意見
全体的には良さそうだが、ToSpan 構文が少し引っかかった
let span = 5.days().hours(8).minutes(1);最初の数値だけが前に出て、残りは関数の引数に入る形が不自然に感じる
気に入らなければ
let span = Span::new().days(5).hours(8).minutes(1);のようにも書けるし、文字が少し増える程度なので悪くはないuse jiff::{Timestamp, ToSpan};の後で"2024-07-11T01:14:00Z".parse()?が Timestamp としてパースされるところが引っかかったRust はクラスの代わりにインターフェースのようなものを使うと記憶しているが、ライブラリを取り込むと文字列に突然
parse()メソッドが生えて、その一般的な名前のメソッドがTimestampオブジェクトを作る理由が気になる左辺の型が
str.parse()の意味を決めるのか、日付ライブラリと Lisp 式ライブラリがどちらも文字列にparse()を付けたらどうなるのかも気になるなぜ
Timestamp.parse(str)のような構文を使わないのか、というところまで疑問が広がるlet span = 5.days() + 8.hours() + 1.minutes();のほうが好き元の構文は Ruby がやりそうな形に見える
コードを読むときに、まずspan を扱っていることが見えるほうが良い
let span = Span::days(5).hours(8).minutes(1);のほうが良さそう日付/時刻ライブラリの複雑さを過小評価している例を何度も見てきた
「内部表現は UTC/Unix 時刻にすればいい」「期間はナノ秒で表せばいい」「タイムゾーンの代わりにオフセットだけ使えばいい」といった話が繰り返される
そう考えるなら Jiff の設計文書を読んでみるといい: https://github.com/BurntSushi/jiff/blob/master/DESIGN.md
BurntSushi の仕事らしく、優れていて詳しい
Rust の事実上の標準的な日付/時刻ライブラリである chrono との比較も読む価値がある: https://docs.rs/jiff/latest/jiff/_documentation/comparison/i...
シリアライズ/デシリアライズを経ても正しく動く DST 計算、丸め可能な期間、タイムゾーンを認識したカレンダー計算、過去のタイムゾーン衝突の検出といった要素が、ライブラリを正確で有用で使いやすいものにしている
chrono は包括的で「正確な」ライブラリだが、硬直的で使いやすいとは言えなかった
Debian が
rgを標準提供する前からすでに ripgrep を使っていたし、Eclipse のほうが IntelliJ IDEA より優れていると思う人たちがいた時代には JodaTime も使っていただが、その文書のどこにも「期間はナノ秒で表せばいい」への反論はない
ユーザーはタイムゾーンと DST に基づく正しい時刻を見る必要があるが、プログラムはたいていそうではない
複数のタイムゾーンを直接扱う作業でなければ、期間をミリ秒/ナノ秒で表すのは概して非常に安全だ
人間がタイムゾーンや DST を作ったからといって、CPU 内部の時計が毎秒何十億回も刻むという物理現象が変わるわけではない
数日前に地球の半分で落ちなかった OS カーネルを見ても、コード上のタイムアウトがミリ秒単位で表現されている箇所は多い
「30 秒のクールダウン」を実装するときに、現在のタイムゾーンに 30 秒を足し、タイムゾーン・DST・12/24 時間表記まで含む文字列として保存し、うるう秒の有無まで確認しなければならない、というふうに誤解されかねない
単にミリ秒で表した30 秒のタイムアウト/クールダウンを使えばよく、ユーザー基準で 29 秒後に起きるとしても、うるう秒は気にしなくていい
素晴らしいライブラリに見える
BurntSushiがなぜ新しいライブラリを作っているのか気になる
Rustで時刻処理をあまりやったことはないけれど、既存ライブラリに性能問題があるのか、既存APIが使いにくいのか、それとも単に趣味なのか、ほかに理由があるのか知りたい
Java標準ライブラリの実装が良くなく、その問題を解決するためにJodaTimeが登場した
その後Java 8で、JodaTimeの影響を大きく受けた新しいDateTime APIが入り、標準ライブラリにあるためライブラリ作者たちがより広く採用できた
https://www.joda.org/joda-time/
https://www.baeldung.com/java-8-date-time-intro
https://github.com/BurntSushi/jiff/blob/master/DESIGN.md
https://github.com/BurntSushi/jiff/blob/master/COMPARE.md
要約すると、主にAPIとIANAタイムゾーン対応のため
Rustのカレンダーライブラリの状況は、あまり理想的ではない
Pandasには
.tz_convert()と.tz_localize()があり、タイムゾーン変換は事実上それで終わる基準にしている作業は「与えられた日付について、CET/CESTの1日の最初の時刻をUTCで求める」ことだが、Pandasではとても簡単
Chronoでは
NaiveDateを作ってDateTimeに変換し、その後またDateTimeに変換する必要があり、こうした変換に一貫したパターンを見つけられなかったある時はメンバー関数で、ある時はタイムゾーンオブジェクトの静的メソッドになっている
いつか誰かが直してくれることを願っている。Jiffは正しい方向への一歩に見えるが、構文が時々変なので、もっと予測しやすい形が欲しい
date(y, m, d).intz("Europe/Rome")?でできるそこでUTCを得たいなら
with_time_zone(TimeZone::UTC)を追加すればよいJiffがどうすればもっと良くなるのか、構文が変だという部分をもう少し説明してもらえるとありがたい
「起きて、ベイビー、新しいBurntSushiが出たよ」[0]
真面目な話、ここにいるRustユーザーの中で、このクレートがなぜ
tracingのようなものを使わないのか知っている人がいるか気になる重すぎるからかもしれない。クレート基準でサイズがほぼ半分くらいなので
logももちろん問題ないし、タイムゾーン演算の呼び出しまで追跡するのが一般的なユースケースなのかも分からないが、特別な理由があったのか気になる[0] https://knowyourmeme.com/memes/wake-up-babe
logは最小公倍数に近いと思うtracingともよく連携するので、Jiffで使った理由はおそらくそれだと思うcrates.ioができた初期の頃からずっと使われてきたものでもある
Jiffには、特定のログレベルでメッセージを出すこと以外の要件はない
もちろん
tracingでもうまく動いただろうとは思うBurntSushiはRustの正規表現エコシステム全体を作った人でもある
Rustであれ他のエコシステムであれ、既存の時刻ライブラリで最も残念なのはうるう秒対応が不足していること
たいてい内部的にTAIではなくUNIXタイムスタンプを使っているためで、このライブラリも残念ながら違わない
tzifファイルである程度対応する方法はあるようだが、第一級のサポートではない
関連するJiffのIssueに詳しい内容がある: https://github.com/BurntSushi/jiff/issues/7
UNIXタイムスタンプはSI秒の定義を使わず、現在の1日の1/86000を使うため、すべてのUNIX秒の長さが同じではなく、正しい期間計算が壊れる
古い時刻追跡方式との互換性を受け継ぎ、カレンダーのフォーマットを高速にするための妥協だったことは理解するが、正しい妥協だったとは思わない
UNIXタイムスタンプは表現上の関心事をデータに混ぜ込んでしまう
うるう秒は2月29日やタイムゾーンとまったく同じように扱うべきだと思う
hifitimeのような専門の科学ライブラリではなぜ合わないのかも気になるうるう秒の話が出るたび、だいたい抽象的な話に流れていくように見える
現実世界のユースケースと結びつくことはかなり少ない
科学用途は理解しているし、そうした用途は専門ライブラリがうまく扱っていると思う
それ以外にさらに必要なものがあるのかはよく分からない
Jiffは硬いGで発音して「Giff」になるgoのGなのか、ginのGなのか気になる「Jiffは
gemのような柔らかいgのgifと発音します」絶対にダメ!
私はchronoに残る †
† もちろん
chaseのchのように発音して「trono」になるそれで jiff と発音するの、それとも gif と発音するの?
「Jiff は
gemのような soft g のgifと発音します。」ああ、どういうネタか分かった……
GIF は作った人によれば常に「jif」と発音されてきたので、混乱を避けるには、この新しいツールの作者が Jiff を「gif」と発音してくれるといい