Why does UTCDateTime exist?

It’s true that you can express a UTC time using an OffsetDateTime with an offset of 0. However, UTCDateTime is valuable exactly because it doesn’t allow an offset. By using it, you clearly express that you are working only in UTC, and are not storing local time.

Consider the difference in intent between these two classes:

class ChatMessage:
    sent: UTCDateTime
    content: str
class ChatMessage:
    sent: OffsetDateTime
    content: str

In the first example, it’s clear that you only care about the moment when chat messages were sent. In the second, you communicate that you also store the user’s local time. This intent is crucial for reasoning about the code, and extending it correctly (e.g. with migrations, API endpoints, etc).

Why does LocalSystemDateTime exist?

It’s true that the local system time is irrelevant in server-type applications. However, it is often useful for CLI tools or desktop applications to output or accept times in the user’s local time.

Why not use OffsetDateTime? Because it doesn’t know about the system’s DST changes, while LocalSystemDateTime does. This allows it to correctly add and subtract durations.

Why not use ZonedDateTime? Because it can only express IANA timezones. While a system is often configured with an IANA timezone, it’s not guaranteed to be so. LocalSystemDateTime works with the system’s local timezone, regardless of how it’s configured.

Of course, feel free to work with ZonedDateTime if you know the system’s IANA timezone. You can use the tzlocal library to help with this.

Why does NaiveDateTime exist?

In general, you shouldn’t ignore timezones in a real application. However, there there are cases where you simply don’t know the timezone. For example, when parsing a date from a user input, or when reading datetimes from a file that doesn’t include timezone information. Expressing these as NaiveDateTime makes it clear that the timezone is unknown.

Also, compared to the standard library, whenever.NaiveDateTime is safer to use:

  • It’s a different class, which prevents accidentally mixing it with aware datetimes.

  • It doesn’t have a .now() method, removing a common source of mistakenly naive datetimes.

  • Conversions to aware datetimes are explicit about assumptions being made:

    >>> n = NaiveDateTime(2022, 1, 1, 12)
    >>> n.assume_zoned("Europe/Berlin")
    ZonedDateTime(2022-01-01 12:00:00+01:00[Europe/Berlin])

Why can’t OffsetDateTime add or subtract durations?

OffsetDateTime does not support addition or subtraction of time deltas. This is a deliberate decision to avoid an infamous pitfall. In practice, fixed-offset datetimes are commonly used to express a time at which something occurs at a specific location. But for many locations, the offset changes throughout the year (due to DST or political decisions). Allowing users to add/subtract from fixed-offset datetimes gives them the impression that they are doing valid arithmetic, while in actuality they are setting themselves up for DST-bugs (which, again, are rampant).

An example:

>>> departure = OffsetDateTime(2024, 11, 3, hour=1, offset=-7)
>>> departure.add(hours=2)  # a 2 hour delay
OffsetDateTime(2024-11-03 03:00:00-07:00)

While this is correct in theory, it may not be what the user intended. Does the -7:00 offset correspond to Denver, or Phoenix? It would be correct in Phoenix (which doesn’t observe DST), but in Denver, the correct result would actually be 02:00:00-06:00 — an hour earlier on the clock!

For whenever, preventing a damaging pitfall weighs heavier than supporting a more theoretical usage pattern. This is consisent with other libraries that emphasize correctness, such as NodaTime. If you do need to perform arithmetic on a fixed-offset datetime, you should make the location explicit by converting it to a ZonedDateTime first:

>>> departure.as_zoned("America/Denver").add(hours=2)
ZonedDateTime(2024-11-03 02:00:00-06:00[America/Denver])
>>> departure.as_zoned("America/Phoenix").add(hours=2)
ZonedDateTime(2024-11-03 03:00:00-07:00[America/Phoenix])
>>> # not recommended, but possible:
>>> departure.as_utc().add(hours=2).as_offset(departure.offset)
OffsetDateTime(2024-11-03 03:00:00-07:00)


OffsetDateTime does support calculating the difference between two datetimes, because this isn’t affected by DST changes:

>>> a = OffsetDateTime(2024, 11, 3, hour=1, offset=-7)
>>> a - OffsetDateTime(2024, 11, 3, hour=3, offset=4)

Are leap seconds supported?

Leap seconds are unsupported. Taking leap seconds into account is a complex and niche feature, which is not needed for the vast majority of applications. This decision is consistent with other modern libraries (e.g. NodaTime, Temporal) and standards (RFC 5545, Unix time) which do not support leap seconds.

Nonetheless, these improvements are possible in the future:

  • Allow parsing of leap seconds, e.g. 23:59:60.

  • Allow representation of leap seconds (similar to rust Chrono)

How is the performance?

Because whenever currently wraps the standard library, head-to-head performance will be slightly slower. However, because whenever removes the need for many runtime checks, it may result in a net performance gain in real-world applications.

A Rust extension is planned once the API stabilizes, which will provide a significant performance boost for certain operations.

Why isn’t it a drop-in replacement for the standard library?

Fixing the issues with the standard library requires a different API. Keeping the same API would mean that the same issues would remain. Also, inheriting from the standard library would result in brittle code: many popular libraries expect datetime exactly, and don’t work with subclasses.

Is it production-ready?

The core functionality is complete and mostly stable. The goal is to reach 1.0 soon, but the API may change until then. Of course, it’s still a relatively young project, so the stability relies on you to try it out and report any issues!