Metadata-Version: 2.4
Name: whenever
Version: 0.11.0b0
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta
Classifier: Programming Language :: Python
Classifier: Programming Language :: Rust
Classifier: Typing :: Typed
Requires-Dist: tzdata>=2020.1 ; sys_platform == 'win32'
Requires-Dist: tzlocal>=4.0 ; sys_platform != 'darwin' and sys_platform != 'linux'
License-File: LICENSE
License-File: LICENSE-THIRD-PARTY
Summary: Type-safe datetimes for Python that get DST right, in Rust or pure Python
Keywords: datetime,typesafe,rust,date,time,timezone,utc,zoneinfo,tzdata,tzdb
Author-email: Arie Bovenberg <a.c.bovenberg@gmail.com>
Maintainer-email: Arie Bovenberg <a.c.bovenberg@gmail.com>
License-Expression: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Changelog, https://github.com/ariebovenberg/whenever/blob/main/CHANGELOG.md
Project-URL: Documentation, https://whenever.readthedocs.io
Project-URL: Issues, https://github.com/ariebovenberg/whenever/issues
Project-URL: Repository, https://github.com/ariebovenberg/whenever

# ⏰ Whenever

[![](https://img.shields.io/pypi/v/whenever.svg?color=blue)](https://pypi.python.org/pypi/whenever)
[![](https://img.shields.io/pypi/pyversions/whenever.svg)](https://pypi.python.org/pypi/whenever)
[![](https://img.shields.io/pypi/l/whenever.svg?color=blue)](https://pypi.python.org/pypi/whenever)
[![](https://img.shields.io/github/actions/workflow/status/ariebovenberg/whenever/checks.yml?branch=main)](https://github.com/ariebovenberg/whenever)
[![](https://img.shields.io/readthedocs/whenever.svg)](http://whenever.readthedocs.io/)
[![](https://img.shields.io/pypi/dm/whenever)](https://pypi.python.org/pypi/whenever)


**Type-safe datetimes for Python that get DST right. Rust or pure Python—your choice.**

Do you cross your fingers every time you work with Python's datetime—hoping that you didn't
mix naive and aware, or run into one of its [other pitfalls](https://whenever.readthedocs.io/en/latest/stdlib-pitfalls/index.html)?

*Whenever* takes the guesswork out, bringing **well-established concepts** from
[modern libraries](#acknowledgements) in other languages to Python.
Mixing up naive and aware becomes a **type error** instead of a bug you find in production,
and DST is handled correctly in **all** arithmetic.
It's also **way faster** than other third-party libraries, and usually the standard library as well.
Rather not depend on a Rust extension? A **pure-Python backend** is available too.

  <p align="center">
    <picture align="center">
        <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ariebovenberg/whenever/main/benchmarks/comparison/graph-dark.svg">
        <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/ariebovenberg/whenever/main/benchmarks/comparison/graph-light.svg">
        <img alt="Shows a bar chart with benchmark results." src="https://raw.githubusercontent.com/ariebovenberg/whenever/main/benchmarks/comparison/graph-light.svg">
    </picture>
  </p>

  <p align="center" style="font-size: 14px">
    <i>Parse, normalize, compare to now, shift, change time zone, and format (1M times)</i>
  </p>

<div align="center">


[📖 Docs](https://whenever.readthedocs.io) |
[🐍 PyPI](https://pypi.org/project/whenever/) |
[🚀 Changelog](https://whenever.readthedocs.io/en/latest/changelog.html) |
[❓ FAQ](https://whenever.readthedocs.io/en/latest/faq.html) |
[🐛 Issues](https://github.com/ariebovenberg/whenever/issues) |
[💬 Q&A](https://github.com/ariebovenberg/whenever/discussions)

</div>

> ⚠️ Note: 0.11 ships the 1.0 API. The deprecated spellings keep working
> as shims until 1.0, and the changelog's migration table lists each one.
> 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:

   ```python
   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* time zones.
   If you think this is surprising, you
   [are](https://github.com/python/cpython/issues/91618)
   [not](https://github.com/python/cpython/issues/116035)
   [alone](https://github.com/python/cpython/issues/112638).

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!

   ```python
   # 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:

<div align="center">

|                   | Whenever | datetime | Arrow | Pendulum |
|-------------------|:--------:|:--------:|:-----:|:--------:|
|      DST-safe     |     ✅    |     ❌    |   ❌   |     ⚠️    |
| Typed aware/naive |     ✅    |     ❌    |   ❌   |     ❌    |
|        Fast       |     ✅    |     ✅    |   ❌   |     ❌    |

</div>

[**Arrow**](https://pypi.org/project/arrow/)
puts a friendlier API on top of the standard library's model — pitfalls
included. Its single do-it-all type and anything-goes `arrow.get()`
make mistakes *harder* for type checkers to catch, not easier.
[Full comparison →](https://whenever.readthedocs.io/en/latest/why-not-arrow.html)

[**Pendulum**](https://pypi.org/project/pendulum/)
promised to fix `datetime` as a drop-in replacement — but a subclass
can't change behavior *and* stay drop-in. The result is an API built on
guesswork: `+` inspects the call stack to guess which semantics the caller
expects, missing time zones become UTC, and incomplete input is completed
from the clock. Bugs follow: `today()` can even return yesterday.
[Full comparison →](https://whenever.readthedocs.io/en/latest/why-not-pendulum.html)

## Why use whenever?

- 🌐 DST-safe arithmetic
- 🛡️ Typesafe API prevents common bugs
- ✅ Fixes issues [arrow/pendulum don't](https://dev.arie.bovenberg.net/blog/python-datetime-pitfalls/#datetime-library-scorecard)
- ⚖️  Based on proven and [familiar concepts](https://www.youtube.com/watch?v=saeKBuPewcU)
- ⚡️ Unmatched performance
- 💎 Thoroughly tested and documented
- 📆 Support for date arithmetic
- ⏱️ Nanosecond precision
- 🗄️ [SQLAlchemy support](https://pypi.org/project/whenever-sqlalchemy/)
- 🪃 Pydantic support
- 🦀 Rust!—or the [pure-Python backend](https://whenever.readthedocs.io/en/latest/faq.html#how-can-i-use-the-pure-python-backend), if you prefer
- 🧵 Free-threading support (beta)
- 🚀 Supports per-interpreter GIL

## Quickstart

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

# Identify moments in time, without time zone/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 plain (local) datetime can't accidentally mix with exact types.
# You need to explicitly convert it and handle ambiguity.
>>> party_invite = PlainDateTime("2023-10-28 22:00")
>>> party_invite.add(hours=6)
  NaiveArithmeticWarning: Shifting a PlainDateTime by exact time units does not account for time zone transitions [...]
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 (ISO 8601, RFC 3339, RFC 2822)
>>> 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](https://whenever.readthedocs.io/en/latest/guide/choosing-a-type.html)
or [API reference](https://whenever.readthedocs.io/en/latest/reference/datetime.html).

## Limitations

- Supports the proleptic Gregorian calendar between 1 and 9999 AD
- Time zone offsets are limited to whole seconds (consistent with the IANA time zone database)

## 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](https://nodatime.org/)** and **[Joda Time](https://www.joda.org/joda-time/)**
  pioneered the concept-per-type approach that makes *whenever* possible.
  Noda Time's type hierarchy directly inspired *whenever*'s design.

- **[Temporal](https://tc39.es/proposal-temporal/docs/)** (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 backend
  for low-level date/time handling.

- *Whenever* also borrows a few nifty ideas from [Jiff](https://github.com/BurntSushi/jiff):
  A modern datetime library in Rust which takes inspiration from Temporal.

- The benchmark comparison graph is adapted from the [Ruff](https://github.com/astral-sh/ruff) project.

