---
hide-toc: true
myst:
html_meta:
description: >-
Why using a single datetime type for both naive and aware values makes it
impossible to enforce the distinction with type annotations.
---
# One type for two incompatible concepts
Python uses a single `datetime` type to represent two fundamentally different concepts:
* **Naive datetimes**, which have no time zone information
* **Aware datetimes**, which are associated with a time zone
These two behave differently, are interpreted differently,
and should never be mixed.
Yet the type system makes no distinction between them.
This becomes especially frustrating in typed code.
There is no way to express, using type annotations,
whether a function expects a naive or an aware `datetime`.
```python
def schedule_at(dt: datetime) -> None:
...
```
Does `dt` represent a local wall-clock time? A UTC timestamp? A zoned time?
The type gives you no way to say.
This makes it impossible to statically enforce one of the most important
invariants in date-time code.
As a result, mistakes that should be caught early often surface only
at runtime—or worse, much later.
## How `whenever` solves this
`whenever` strictly separates datetimes with and without time zone information:
* {class}`~whenever.PlainDateTime` is the equivalent of a "naive" time
* {class}`~whenever.ZonedDateTime` is the equivalent of an "aware" time with
`ZoneInfo` attached
* {class}`~whenever.Instant` is the equivalent of an "aware" time with `UTC` attached
This makes type annotations precise and self-documenting:
```python
def schedule_at(dt: ZonedDateTime) -> None:
...
```