Whenever – Python向けの型安全かつDST安全な日時処理ライブラリ
(github.com/ariebovenberg)- 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はInstant、ZonedDateTime、PlainDateTimeのように用途別の型を分け、型チェッカーがミスを検出できるようにする - 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 datetimeとaware 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をサポート
使用例の流れ
Instant、ZonedDateTime、PlainDateTimeのような明示的な型をインポートし、異なるユースケースを表現するInstant.now()は、タイムゾーンやカレンダーの複雑さなしに特定の時点を識別するnow.to_tz("Europe/Paris")のように明示的にタイムゾーンを変換するPlainDateTime("2023-10-28 22:00")は他の型と偶然混ざらないadd(hours=6)を呼ぶと、ローカル時刻の調整がDSTを無視するというNaiveArithmeticWarningが発生するassume_tz("Europe/Amsterdam")でタイムゾーンを明示的に仮定する必要がある
ZonedDateTimeでadd(hours=6)を実行すると、DST移行を反映して2023-10-29 03:00:00+01:00[Europe/Amsterdam]という結果を返す- 比較、丸めと切り捨て、ISO8601・RFC3339・RFC2822形式のパース、カスタムパターン形式をサポート
- 必要に応じて標準ライブラリの
datetimeへ変換したり、その逆に変換したりできる - 詳しい使い方はfeature overviewとAPI 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 TimeとJoda Timeは概念ごとの型アプローチを切り開き、Noda Timeの型階層がWheneverの設計に直接的な着想を与えた
- TemporalはDSTの曖昧さや丸め周辺の複雑なケース処理に着想を与えた
- Pythonの
datetimeモジュールは、Wheneverの純粋なPython実装における低レベル日時処理で広く使われている - Jiffの一部アイデアも借用している
- ベンチマーク比較グラフはRuffプロジェクトを基に翻案された
まだコメントはありません。