2 ポイント 投稿者 GN⁺ 2024-07-23 | 1件のコメント | WhatsAppで共有
  • 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ドキュメント
    • chronotimehifitimeicuとの比較ドキュメント
    • 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.rsZoned::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への抜け道として機能する

プラットフォーム別のタイムゾーン処理

  • 日付・時刻ライブラリにおけるプラットフォームサポートの問題は、主にタイムゾーン対応に関係している
  • 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タイムゾーン識別子へマッピングする
  • 他プラットフォームの対応は今後時間とともに増える見込みだが、テストが難しい珍しいプラットフォームについては、コントリビューターからの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-sysportable-atomicのような外部依存関係のMSRVはJiffでは制御できない
  • Rustエコシステムの慣例に従い、JiffはMSRV引き上げをsemver非互換変更とは見なさないが、MSRVの引き上げはminor versionアップデートに限定する

1件のコメント

 
GN⁺ 2024-07-23
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 は包括的で「正確な」ライブラリだが、硬直的で使いやすいとは言えなかった

    • BurntSushi の ripgrep が好きで、Emacs から直接呼び出していつも使っている
      Debian が rg を標準提供する前からすでに ripgrep を使っていたし、Eclipse のほうが IntelliJ IDEA より優れていると思う人たちがいた時代には JodaTime も使っていた
      だが、その文書のどこにも「期間はナノ秒で表せばいい」への反論はない
      ユーザーはタイムゾーンと DST に基づく正しい時刻を見る必要があるが、プログラムはたいていそうではない
      複数のタイムゾーンを直接扱う作業でなければ、期間をミリ秒/ナノ秒で表すのは概して非常に安全だ
      人間がタイムゾーンや DST を作ったからといって、CPU 内部の時計が毎秒何十億回も刻むという物理現象が変わるわけではない
      数日前に地球の半分で落ちなかった OS カーネルを見ても、コード上のタイムアウトがミリ秒単位で表現されている箇所は多い
      「30 秒のクールダウン」を実装するときに、現在のタイムゾーンに 30 秒を足し、タイムゾーン・DST・12/24 時間表記まで含む文字列として保存し、うるう秒の有無まで確認しなければならない、というふうに誤解されかねない
      単にミリ秒で表した30 秒のタイムアウト/クールダウンを使えばよく、ユーザー基準で 29 秒後に起きるとしても、うるう秒は気にしなくていい
    • 「ser/de を経ても動く」という言葉を見て、ようやく Serde というクレート名がなぜそうなのか分かった
    • 設計文書を教えてくれてありがとう。リポジトリを最初にざっと見たときは見落としていたが、よく書かれた文書で、気になっていた点を多く解消してくれた
    • 日付/時刻の狂気を楽しく取っつきやすく見たいなら、Kip Cole の ElixirConf 2022 の発表が良い: https://www.youtube.com/watch?v=4VfPvCI901c
    • 日付/時刻が簡単だと思っている人を、スケジューリングや日付処理コードの近くに置いてはいけない
  • 素晴らしいライブラリに見える
    BurntSushiがなぜ新しいライブラリを作っているのか気になる
    Rustで時刻処理をあまりやったことはないけれど、既存ライブラリに性能問題があるのか、既存APIが使いにくいのか、それとも単に趣味なのか、ほかに理由があるのか知りたい

  • 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の正規表現エコシステム全体を作った人でもある

    • Redditで何かについて議論していて、相手が誰なのかに気づいて「もう結構です」となった記憶がある
  • 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」になる

    • 非ネイティブなのだが、hard Gとは正確にはどういう意味? goのGなのか、ginのGなのか気になる
  • 「Jiffはgemのような柔らかいgのgifと発音します」
    絶対にダメ!

    • しばらくHacker Newsの1位に上がって楽しい時間を過ごしたが、今や致命的な欠陥が明らかになったようだ
      私はchronoに残る †
      † もちろんchasechのように発音して「trono」になる
    • https://x.com/drgonzo123/status/527520297512345600
    • 変だな、Jiffは硬い「J」で発音するのだと思っていた
    • GIFではなくGIFと発音するってこと?!
  • それで jiff と発音するの、それとも gif と発音するの?

    • https://github.com/BurntSushi/jiff/blob/master/DESIGN.md#why...
      「Jiff は gem のような soft g の gif と発音します。」
      ああ、どういうネタか分かった……
    • 発音は作った人が決めるものだと思う
      GIF は作った人によれば常に「jif」と発音されてきたので、混乱を避けるには、この新しいツールの作者が Jiff を「gif」と発音してくれるといい
    • 時刻処理ライブラリだから、自分は単に biff と呼ぶことにする