1 ポイント 投稿者 GN⁺ 2025-04-14 | まだコメントはありません。 | WhatsAppで共有
  • Wheneverは、Pythonのdatetimeにおけるnaive/awareの混在やDST計算ミスを型で防ぎ、Rust実装または純粋なPython実装で利用できる日時ライブラリ
  • 標準ライブラリのdatetimeはDSTを常に反映するわけではなく、datetime(2023, 3, 25, 22, tzinfo=ZoneInfo("Europe/Paris")) + timedelta(hours=8)が、DSTで1時間が飛ばされているにもかかわらず、7時ではなく6時を返す例が示されている
  • 型システムはdatetimeひとつでnaiveとawareを区別できないが、WheneverはInstantZonedDateTimePlainDateTimeのように用途別の型を分け、型チェッカーがミスを検出できるようにする
  • ArrowとPendulumは比較表でDST安全性、typed aware/naive、性能をすべて満たせておらず、WheneverはDST-safe、typed aware/naive、fastの各項目をすべてサポートすると示されている
  • まだ1.0前のためminorリリースでAPIが変わる可能性があり、変更点はchangelogで説明され、型チェッカーやIDEが調整を助けられる

Wheneverが解決しようとしている問題

  • Wheneverは、Pythonで正しく型チェック可能な日時コードを書くのを助けるライブラリ
  • Rust実装を提供し、Rustを望まないユーザー向けに純粋なPython版もある
  • 現代的な他言語の日時ライブラリで使われている実証済みの概念を基に設計されている
  • 他のサードパーティライブラリよりはるかに高速で、通常は標準ライブラリよりも速いと紹介されている
  • 1.0リリースは長期APIをさらに磨き込むため保留中で、特にduration関連のフィードバックを受け付けている

標準ライブラリdatetimeの限界

  • Pythonのdatetimeは20年以上にわたって成長してきた結果、現代的な日時ライブラリに期待されるやり方とずれている部分がある
  • DST計算の問題

    • datetimeは**Daylight Saving Time(DST)**を常に考慮するわけではない
    • サンプルコードでは、Europe/Parisタイムゾーンで2023年3月25日22時に8時間を足すと、DSTで1時間が飛ばされるため7時になるべきところ、6時を返す
    • これはバグではなく、2つのタイムゾーンが関係する計算でのみDSTを考慮するという設計上の判断
  • naive/awareの型区別の問題

    • datetime型ひとつだけでは、naive datetimeaware datetimeを型システム上で区別できない
    • def schedule_meeting(at: datetime) -> None: ...のような関数シグネチャでは、naiveを期待しているのかawareを期待しているのか分からない

Arrow・Pendulumとの比較

  • 比較表によれば、WheneverはDST-safe、typed aware/naive、fastをすべて満たす
  • datetimeはfast項目のみを満たし、DST-safeとtyped aware/naiveは満たさない
  • Arrowは標準ライブラリより親しみやすいAPIを提供しようとしているが、根本的な問題は解決していない
    • 同じfootgunを残している
    • 型をarrow.Arrowひとつに減らす判断により、型チェッカーがミスを見つけるのがさらに難しくなっている
  • Pendulumは2016年により良いDST処理と性能改善を掲げたが、一部のDST関連の落とし穴だけを修正した
    • 性能は時間の経過とともに大きく低下した
    • 直近4年間でリリースが2つしかない長いメンテナンス停滞状態にあり、深刻で古いissueが多く残っている

主な機能

  • DST-safe arithmeticをサポート
  • 型安全なAPIでよくあるバグを防ぐ
  • ArrowとPendulumが修正できなかった問題を修正
  • 実証済みでなじみのある概念に基づく
  • 高い性能を提供
  • テストとドキュメントが充実
  • 日付演算をサポート
  • ナノ秒精度をサポート
  • SQLAlchemyサポートを提供
  • Pydanticサポートはベータ状態
  • Rust実装と純粋なPythonオプションを提供
  • free-threadingサポートはベータ状態
  • per-interpreter GILをサポート

使用例の流れ

  • InstantZonedDateTimePlainDateTimeのような明示的な型をインポートし、異なるユースケースを表現する
  • Instant.now()は、タイムゾーンやカレンダーの複雑さなしに特定の時点を識別する
  • now.to_tz("Europe/Paris")のように明示的にタイムゾーンを変換する
  • PlainDateTime("2023-10-28 22:00")は他の型と偶然混ざらない
    • add(hours=6)を呼ぶと、ローカル時刻の調整がDSTを無視するというNaiveArithmeticWarningが発生する
    • assume_tz("Europe/Amsterdam")でタイムゾーンを明示的に仮定する必要がある
  • ZonedDateTimeadd(hours=6)を実行すると、DST移行を反映して2023-10-29 03:00:00+01:00[Europe/Amsterdam]という結果を返す
  • 比較、丸めと切り捨て、ISO8601・RFC3339・RFC2822形式のパース、カスタムパターン形式をサポート
  • 必要に応じて標準ライブラリのdatetimeへ変換したり、その逆に変換したりできる
  • 詳しい使い方はfeature overviewAPI referenceで確認できる

制約と安定性ポリシー

  • サポート範囲は1年から9999年までのproleptic Gregorian calendar
  • タイムゾーンオフセットはIANA TZ DBと一貫するように秒単位の整数に制限される
  • Wheneverはsemantic versioningに従う
  • 1.0以前はminorリリースでAPIが変わる可能性がある
  • breaking changeはchangelogで詳しく説明される
  • APIが完全に型付けされているため、型チェッカーやIDEがAPI変更への対応を助けられる

ライセンスと設計への影響

  • WheneverはMIT Licenseで配布される
  • binary wheelにはMIT、Apache-2.0など同様に寛容なライセンスのRust依存関係が含まれる
  • Noda TimeJoda Timeは概念ごとの型アプローチを切り開き、Noda Timeの型階層がWheneverの設計に直接的な着想を与えた
  • TemporalはDSTの曖昧さや丸め周辺の複雑なケース処理に着想を与えた
  • Pythonのdatetimeモジュールは、Wheneverの純粋なPython実装における低レベル日時処理で広く使われている
  • Jiffの一部アイデアも借用している
  • ベンチマーク比較グラフはRuffプロジェクトを基に翻案された

まだコメントはありません。

まだコメントはありません。