Performance¶
whenever optimizes for three goals that are sometimes in tension:
Runtime speed — operations should be as fast as possible
Import time —
import whenevershould feel instantPackage size — the wheel should stay small for fast/slim installs
These goals can conflict: aggressive inlining improves runtime speed but
increases binary size, which in turn inflates import time.
whenever targets a balance, sacrificing a bit of runtime speed in cold
code paths to keep the module compact and fast to import.
Test environment
All benchmarks on this page were run on an Apple M1 Pro (32 GB, macOS 26.5) using Python 3.14.6 (PGO+LTO build). Import-time measurements use warm page cache.
Runtime speed¶
whenever is compared against Python’s standard library (+ dateutil),
Arrow, and Pendulum
across common datetime operations.
Lower is better. Bars exceeding the axis cutoff are annotated with >.
Why is whenever faster?
No layering. Arrow and Pendulum wrap Python’s
datetime.datetimerather than replacing it. Every operation pays the overhead of crossing extra Python abstraction layers thatwheneveravoids entirely.Optimised parsing and formatting. The Rust extension uses hand-written, single-pass byte-level parsers and formatters: no regex, no intermediate string objects.
Front-loaded computation. Every
ZonedDateTimestores its UTC offset at construction time. Operations like “normalize to UTC” or “subtract two instants” become simple integer arithmetic with no timezone database lookup at operation time.Compiled core. The default wheel is a Rust extension, giving C-level performance with safe, auditable code. The pure-Python fallback still benefits from the front-loaded computation model and outperforms Arrow on most simple operations.
What about the pure-Python version of whenever?
For simple operations — now(), ISO parsing,
UTC normalization — it is noticeably faster than Arrow and Pendulum. For
timezone-heavy operations such as ZonedDateTime construction or timezone
conversion it is slower, as those use pure-Python timezone code instead
of the C-optimized zoneinfo module.
Overall it is in the same ballpark as Arrow and Pendulum.
Import time¶
import whenever is nearly free because the package defers all heavy
work until the first attribute access.
import datetime is faster on standard CPython builds because datetime.py
is a thin wrapper around the built-in C module _datetime, which imports
without loading a separate shared library. Third-party packages cannot match
that.
Other libraries shown for context.
Import time is mainly kept low through lazy loading of submodules and dependencies:
The
__init__.pyuses module-level__getattr__(PEP 562) to defer the extension load until first access. Code that importswheneverbut doesn’t use it pays essentially nothing.Dependencies (
datetime,zoneinfo,pydantic,typing) are imported on first use, not at module load.
Package size¶
The chart below compares wheel sizes of whenever against other
datetime libraries, as well as some unrelated libraries for context.
A pure-Python wheel is also available for environments where install size
or platform coverage matters more than runtime speed.
whenever’s focus on runtime speed and rich API means it is relatively large.
However, it keeps the wheel size reasonable through careful design choices:
Several types (
Weekday,YearMonth,MonthDay,IsoWeekDate) are implemented only in Python even when the extension is active, keeping the native binary focused on the performance-critical datetime types.Inlining is used judiciously: hot code paths are optimized, while cold paths are prevented from inflating the binary size.
Trade-offs not taken:
Full, descriptive docstrings (almost 100 KB) are included in the wheel.
Panics in Rust code are caught and converted to Python exceptions, which requires unwinding tables and increases binary size.
orjsoncompiles withpanic = "abort"to avoid this overhead, butwheneverprioritizes safety and debuggability over minimal size.Rust extensions have more overhead per method than C extensions, but this is worth the safety and maintainability benefits.
Running the benchmarks yourself¶
See benchmarks/comparison/README.md for setup instructions.
make bench-compare # full comparison run
make bench-compare-fast # quick comparison run
make bench-compare-docs # full run and update these charts