whenever 0.10.0


pip install whenever

  Latest version

Released: Apr 05, 2026


Meta
Author: Arie Bovenberg
Maintainer: Arie Bovenberg
Requires Python: >=3.10

Classifiers

Development Status
  • 4 - Beta

Intended Audience
  • Developers

Operating System
  • MacOS
  • Microsoft :: Windows
  • POSIX :: Linux

Programming Language
  • Python :: 3
  • Python :: 3.10
  • Python :: 3.11
  • Python :: 3.12
  • Python :: 3.13
  • Python :: 3.14
  • Python :: Implementation :: CPython
  • Python :: Implementation :: PyPy
  • Python :: Free Threading :: 2 - Beta
  • Python
  • Rust

Typing
  • Typed

⏰ Whenever

Typed and DST-safe datetimes for Python, available in Rust or pure Python.

Do you cross your fingers every time you work with Python's datetime—hoping that you didn't mix naive and aware? or that you avoided its other pitfalls? There’s no way to be sure...

✨ Until now! ✨

Whenever helps you write correct and type checked datetime code, using well-established concepts from modern libraries in other languages. It's also way faster than other third-party libraries, and usually the standard library as well. Don't buy the Rust hype?—don't worry: a pure Python version is available as well.

Shows a bar chart with benchmark results.

Parse, normalize, compare to now, shift, change timezone, and format (1M times)

⚠️ Note: Holding off on 1.0 a little longer so we can get the API just right for the long term—feedback (especially on durations) is still very welcome. Leave a ⭐️ on GitHub if you'd like to see how this project develops!

Why not the standard library?

Over 20+ years, Python's datetime has grown out of step with what you'd expect from a modern datetime library. Two points stand out:

  1. It doesn't always account for Daylight Saving Time (DST). Here is a simple example:

    bedtime = datetime(2023, 3, 25, 22, tzinfo=ZoneInfo("Europe/Paris"))
    full_rest = bedtime + timedelta(hours=8)
    # It returns 6am, but should be 7am—because we skipped an hour due to DST!
    

    Note this isn't a bug, but a design decision that DST is only considered when calculations involve two timezones. If you think this is surprising, you are not alone.

  2. Typing can't distinguish between naive and aware datetimes. Your code probably only works with one or the other, but there's no way to enforce this in the type system!

    # Does this expect naive or aware? Can't tell!
    def schedule_meeting(at: datetime) -> None: ...
    

Why not other libraries?

There are two other popular third-party libraries, but they don't (fully) address these issues. Here's how they compare to whenever and the standard library:

Whenever datetime Arrow Pendulum
DST-safe ⚠️
Typed aware/naive
Fast

Arrow is probably the most historically popular 3rd party datetime library. It attempts to provide a more "friendly" API than the standard library, but doesn't address the core issues: it keeps the same footguns, and its decision to reduce the number of types to just one (arrow.Arrow) means that it's even harder for typecheckers to catch mistakes.

Pendulum arrived on the scene in 2016, promising better DST-handling, as well as improved performance. However, it only fixes some DST-related pitfalls, and its performance has significantly degraded over time. Additionally, it's in a long maintenance slump with only two releases in the last four years, while many serious and long-standing issues remain unaddressed.

Why use whenever?

  • 🌐 DST-safe arithmetic
  • 🛡️ Typesafe API prevents common bugs
  • ✅ Fixes issues arrow/pendulum don't
  • ⚖️ Based on proven and familiar concepts
  • ⚡️ Unmatched performance
  • 💎 Thoroughly tested and documented
  • 📆 Support for date arithmetic
  • ⏱️ Nanosecond precision
  • 🪃 Pydantic support (beta)
  • 🦀 Rust!—but with a pure-Python option
  • 🧵 Free-threading support (beta)
  • 🚀 Supports per-interpreter GIL

Quickstart

>>> from whenever import (
...    # Explicit types for different use cases
...    Instant,
...    ZonedDateTime,
...    PlainDateTime,
... )

# Identify moments in time, without timezone/calendar complexity
>>> now = Instant.now()
Instant("2024-07-04 10:36:56Z")

# Simple, explicit conversions
>>> now.to_tz("Europe/Paris")
ZonedDateTime("2024-07-04 12:36:56+02:00[Europe/Paris]")

# A 'naive' datetime can't accidentally mix with other types.
# You need to explicitly convert it and handle ambiguity.
>>> party_invite = PlainDateTime("2023-10-28 22:00")
>>> party_invite.add(hours=6)
  NaiveArithmeticWarning: Adjusting a local time ignores DST [...]
PlainDateTime("2023-10-29 04:00")
>>> party_starts = party_invite.assume_tz("Europe/Amsterdam")
ZonedDateTime("2023-10-28 22:00:00+02:00[Europe/Amsterdam]")

# DST-safe arithmetic
>>> party_starts.add(hours=6)
ZonedDateTime("2023-10-29 03:00:00+01:00[Europe/Amsterdam]")

# Comparison and equality
>>> now > party_starts
True

# Rounding and truncation
>>> now.round("minute", increment=15)
Instant("2024-07-04 10:30:00Z")

# Formatting & parsing common formats (ISO8601, RFC3339, RFC2822)
>>> now.format_rfc2822()
"Thu, 04 Jul 2024 10:36:56 GMT"
# Custom pattern formatting and parsing
>>> party_starts.format("MMM DD, hh:mm zz")
"Oct 28, 22:00 CEST"

# If you must: you can convert to/from the standard lib
>>> now.to_stdlib()
datetime.datetime(2024, 7, 4, 10, 36, 56, tzinfo=datetime.timezone.utc)

Read more in the feature overview or API reference.

Limitations

  • Supports the proleptic Gregorian calendar between 1 and 9999 AD
  • Timezone offsets are limited to whole seconds (consistent with IANA TZ DB)

Stability policy

Whenever follows semantic versioning. Until the 1.0 version, the API may change with minor releases. Breaking changes will be meticulously explained in the changelog. Since the API is fully typed, your typechecker and/or IDE will help you adjust to any API changes.

License

Whenever is licensed under the MIT License. The binary wheels contain Rust dependencies which are licensed under similarly permissive licenses (MIT, Apache-2.0, and others). For more details, see the licenses included in the distribution.

Acknowledgements

Whenever draws from decades of datetime library design across multiple languages:

  • Noda Time and Joda Time pioneered the concept-per-type approach that makes whenever possible. Noda Time's type hierarchy directly inspired whenever's design.

  • Temporal (JavaScript proposal) provided inspiration for handling complex cases around DST ambiguity and rounding. After years of TC39 design work, Temporal's API is extraordinarily thorough. Whenever benefits from those hard-won insights.

  • Python's datetime module is used extensively in whenever's pure-Python implementation for low-level date/time handling.

  • Whenever also borrows a few nifty ideas from Jiff: A modern datetime library in Rust which takes inspiration from Temporal.

  • The benchmark comparison graph is adapted from the Ruff project.

0.10.0 Apr 05, 2026
0.10.0b4 Apr 04, 2026
0.10.0b3 Apr 02, 2026
0.10.0b2 Mar 22, 2026
0.10.0b1 Mar 20, 2026
0.10.0a1 Mar 19, 2026
0.9.5 Jan 11, 2026
0.9.5rc0 Jan 11, 2026
0.9.4 Dec 18, 2025
0.9.4rc2 Dec 13, 2025
0.9.4rc1 Dec 12, 2025
0.9.4rc0 Dec 12, 2025
0.9.3 Oct 16, 2025
0.9.2 Sep 29, 2025
0.9.1 Sep 28, 2025
0.9.0 Sep 25, 2025
0.9.0rc0 Sep 21, 2025
0.8.10 Oct 16, 2025
0.8.9 Sep 21, 2025
0.8.9rc0 Sep 21, 2025
0.8.8 Jul 24, 2025
0.8.7 Jul 18, 2025
0.8.6 Jun 23, 2025
0.8.5 Jun 09, 2025
0.8.4 May 28, 2025
0.8.4rc1 May 27, 2025
0.8.4rc0 May 23, 2025
0.8.3 May 22, 2025
0.8.2 May 21, 2025
0.8.1 May 21, 2025
0.8.1rc1 May 20, 2025
0.8.0 May 01, 2025
0.7.3 Mar 19, 2025
0.7.2 Feb 25, 2025
0.7.1 Feb 24, 2025
0.7.0 Feb 21, 2025
0.6.17 Jan 31, 2025
0.6.16 Dec 22, 2024
0.6.15 Dec 11, 2024
0.6.14 Nov 27, 2024
0.6.13 Nov 17, 2024
0.6.12 Nov 08, 2024
0.6.11 Nov 04, 2024
0.6.10 Oct 31, 2024
0.6.9 Sep 12, 2024
0.6.8 Sep 05, 2024
0.6.8rc1 Sep 05, 2024
0.6.7 Aug 06, 2024
0.6.6 Jul 27, 2024
0.6.5 Jul 27, 2024
0.6.4 Jul 26, 2024
0.6.4rc0 Jul 16, 2024
0.6.3 Jul 13, 2024
0.6.2 Jul 05, 2024
0.6.1 Jul 04, 2024
0.6.0 Jul 04, 2024
0.6.0rc1 Jul 04, 2024
0.6.0rc0 Jun 18, 2024
0.5.2 Apr 30, 2024
0.5.1 Apr 02, 2024
0.5.0 Mar 21, 2024
0.4.0 Mar 13, 2024
0.3.4 Feb 07, 2024
0.3.3 Feb 04, 2024
0.3.2 Feb 03, 2024
0.3.1 Feb 01, 2024
0.3.1rc0 Feb 01, 2024
0.3.0 Jan 23, 2024
0.2.1 Jan 20, 2024
0.2.0 Jan 10, 2024
0.1.0 Dec 20, 2023
0.0.4 Nov 30, 2023
0.0.3 Nov 16, 2023
0.0.2 Nov 11, 2023
0.0.1 Nov 10, 2023

Wheel compatibility matrix

Platform CPython 3.10 CPython 3.11 CPython 3.12 CPython 3.13 CPython 3.14 CPython (additional flags: t) 3.13 CPython (additional flags: t) 3.14 Python 3
any
macosx_10_12_x86_64
macosx_11_0_arm64
manylinux1_i686
manylinux2014_aarch64
manylinux2014_armv7l
manylinux2014_ppc64le
manylinux2014_s390x
manylinux2014_x86_64
manylinux_2_17_aarch64
manylinux_2_17_armv7l
manylinux_2_17_ppc64le
manylinux_2_17_s390x
manylinux_2_17_x86_64
manylinux_2_5_i686
musllinux_1_2_aarch64
musllinux_1_2_armv7l
musllinux_1_2_i686
musllinux_1_2_x86_64
win32
win_amd64

Files in release

whenever-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl (584.4KiB)
whenever-0.10.0-cp310-cp310-macosx_11_0_arm64.whl (565.6KiB)
whenever-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (582.1KiB)
whenever-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (635.8KiB)
whenever-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl (619.3KiB)
whenever-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl (631.7KiB)
whenever-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (601.1KiB)
whenever-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl (653.2KiB)
whenever-0.10.0-cp310-cp310-musllinux_1_2_aarch64.whl (754.7KiB)
whenever-0.10.0-cp310-cp310-musllinux_1_2_armv7l.whl (905.2KiB)
whenever-0.10.0-cp310-cp310-musllinux_1_2_i686.whl (858.2KiB)
whenever-0.10.0-cp310-cp310-musllinux_1_2_x86_64.whl (808.3KiB)
whenever-0.10.0-cp310-cp310-win32.whl (546.3KiB)
whenever-0.10.0-cp310-cp310-win_amd64.whl (545.6KiB)
whenever-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl (584.4KiB)
whenever-0.10.0-cp311-cp311-macosx_11_0_arm64.whl (565.6KiB)
whenever-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (582.1KiB)
whenever-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (635.8KiB)
whenever-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl (619.3KiB)
whenever-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl (631.7KiB)
whenever-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (601.1KiB)
whenever-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl (653.2KiB)
whenever-0.10.0-cp311-cp311-musllinux_1_2_aarch64.whl (754.7KiB)
whenever-0.10.0-cp311-cp311-musllinux_1_2_armv7l.whl (905.2KiB)
whenever-0.10.0-cp311-cp311-musllinux_1_2_i686.whl (858.2KiB)
whenever-0.10.0-cp311-cp311-musllinux_1_2_x86_64.whl (808.3KiB)
whenever-0.10.0-cp311-cp311-win32.whl (546.3KiB)
whenever-0.10.0-cp311-cp311-win_amd64.whl (545.5KiB)
whenever-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl (586.1KiB)
whenever-0.10.0-cp312-cp312-macosx_11_0_arm64.whl (566.2KiB)
whenever-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (582.4KiB)
whenever-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (636.2KiB)
whenever-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl (620.5KiB)
whenever-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl (632.4KiB)
whenever-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (602.9KiB)
whenever-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl (654.3KiB)
whenever-0.10.0-cp312-cp312-musllinux_1_2_aarch64.whl (754.4KiB)
whenever-0.10.0-cp312-cp312-musllinux_1_2_armv7l.whl (905.5KiB)
whenever-0.10.0-cp312-cp312-musllinux_1_2_i686.whl (859.7KiB)
whenever-0.10.0-cp312-cp312-musllinux_1_2_x86_64.whl (810.5KiB)
whenever-0.10.0-cp312-cp312-win32.whl (546.3KiB)
whenever-0.10.0-cp312-cp312-win_amd64.whl (547.0KiB)
whenever-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl (586.1KiB)
whenever-0.10.0-cp313-cp313-macosx_11_0_arm64.whl (566.2KiB)
whenever-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (582.4KiB)
whenever-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (636.2KiB)
whenever-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl (620.5KiB)
whenever-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl (632.4KiB)
whenever-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (602.9KiB)
whenever-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl (654.3KiB)
whenever-0.10.0-cp313-cp313-musllinux_1_2_aarch64.whl (754.4KiB)
whenever-0.10.0-cp313-cp313-musllinux_1_2_armv7l.whl (905.5KiB)
whenever-0.10.0-cp313-cp313-musllinux_1_2_i686.whl (859.8KiB)
whenever-0.10.0-cp313-cp313-musllinux_1_2_x86_64.whl (810.5KiB)
whenever-0.10.0-cp313-cp313-win32.whl (546.4KiB)
whenever-0.10.0-cp313-cp313-win_amd64.whl (547.0KiB)
whenever-0.10.0-cp313-cp313t-macosx_10_12_x86_64.whl (585.2KiB)
whenever-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl (566.2KiB)
whenever-0.10.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (583.1KiB)
whenever-0.10.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (637.4KiB)
whenever-0.10.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl (620.9KiB)
whenever-0.10.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl (632.8KiB)
whenever-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (602.1KiB)
whenever-0.10.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl (653.7KiB)
whenever-0.10.0-cp313-cp313t-musllinux_1_2_aarch64.whl (755.2KiB)
whenever-0.10.0-cp313-cp313t-musllinux_1_2_armv7l.whl (907.1KiB)
whenever-0.10.0-cp313-cp313t-musllinux_1_2_i686.whl (859.3KiB)
whenever-0.10.0-cp313-cp313t-musllinux_1_2_x86_64.whl (810.1KiB)
whenever-0.10.0-cp313-cp313t-win32.whl (547.8KiB)
whenever-0.10.0-cp313-cp313t-win_amd64.whl (545.8KiB)
whenever-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl (586.6KiB)
whenever-0.10.0-cp314-cp314-macosx_11_0_arm64.whl (568.4KiB)
whenever-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (583.6KiB)
whenever-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (637.9KiB)
whenever-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl (621.9KiB)
whenever-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl (633.4KiB)
whenever-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (604.7KiB)
whenever-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl (656.1KiB)
whenever-0.10.0-cp314-cp314-musllinux_1_2_aarch64.whl (755.3KiB)
whenever-0.10.0-cp314-cp314-musllinux_1_2_armv7l.whl (907.4KiB)
whenever-0.10.0-cp314-cp314-musllinux_1_2_i686.whl (862.5KiB)
whenever-0.10.0-cp314-cp314-musllinux_1_2_x86_64.whl (812.0KiB)
whenever-0.10.0-cp314-cp314-win32.whl (550.0KiB)
whenever-0.10.0-cp314-cp314-win_amd64.whl (548.1KiB)
whenever-0.10.0-cp314-cp314t-macosx_10_12_x86_64.whl (586.7KiB)
whenever-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl (568.2KiB)
whenever-0.10.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (584.1KiB)
whenever-0.10.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (638.6KiB)
whenever-0.10.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl (622.8KiB)
whenever-0.10.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl (634.4KiB)
whenever-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (604.5KiB)
whenever-0.10.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl (655.8KiB)
whenever-0.10.0-cp314-cp314t-musllinux_1_2_aarch64.whl (756.9KiB)
whenever-0.10.0-cp314-cp314t-musllinux_1_2_armv7l.whl (907.7KiB)
whenever-0.10.0-cp314-cp314t-musllinux_1_2_i686.whl (862.0KiB)
whenever-0.10.0-cp314-cp314t-musllinux_1_2_x86_64.whl (812.3KiB)
whenever-0.10.0-cp314-cp314t-win32.whl (550.6KiB)
whenever-0.10.0-cp314-cp314t-win_amd64.whl (547.2KiB)
whenever-0.10.0-py3-none-any.whl (116.5KiB)
whenever-0.10.0.tar.gz (419.5KiB)
Extras: None
Dependencies:
tzdata (>=2020.1)
tzlocal and (>=4.0)