Skip to content(if available)orjump to list(if available)

Whenever: Typed and DST-safe datetimes for Python

Hasnep

If you've not read the blog post that explains why this library exists I recommend it. It's called "Ten Python datetime pitfalls, and what libraries are (not) doing about it"

https://dev.arie.bovenberg.net/blog/python-datetime-pitfalls...

jwilk

Discussed on HN back then:

https://news.ycombinator.com/item?id=39417231 (147 comments)

barbazoo

I am a seasoned programmer but whenever I deal with datetime objects I do my best with unit tests and then just hope none of these “edge” cases apply to us. Meaning: I have no idea really how it works under the hood.

Now at least there’s an LLM that might spot a bug every now and then so that’s nice.

null

[deleted]

JodieBenitez

Excellent read.

wesselbindt

Ah nice it solves the Liskov violation that the standard library has. In the standard library, dates can be compared with <, and datetimes are dates. But compare a datetime with a date with <, and you get an error. This drove me nuts at work recently.

I wonder what benefits this choice has that outweigh the risks of this behavior.

OJFord

What would you do about equality comparisons?

scott_w

The author wrote a blog post describing that the problem is datetime inherits from date when it shouldn’t. The fact they do but can’t be compared is a compounding of the problem with hidden bugs

heavenlyblue

What do you expect? There are so many ways to handle this behvaiour it's pretty obvious why this is not allowed. Do you take datetime.date and then compare? Do you assume all dates are datetimes at midnight?

MrJohz

The issue isn't that the comparison should be valid, the issue is that datetimes should not be dates. At best, there is a "has a" relationship, but there shouldn't be an "is a" relationship.

pkkm

I think wesselbindt meant that datetimes should not inherit from dates.

null

[deleted]

JimDabell

I’ve tried Arrow, Delorean, and Pendulum, plus the stdlib datetime of course, and settled on Whenever. It fits what I actually do with datetimes better, plus it seems more actively maintained. With the others I always seem to have a nagging feeling in the back of my mind that I am missing a whole load of edge cases. With Pendulum that seems more baked into the API.

apeters

Am I the only one to stick with the std lib, read the docs and changelogs carefully, and implement functions I really need the way my application makes use of them?

I learned the hard way, that dependencies kill projects.

Not saying this isn't great, thanks for creating it! It does have its use cases, of course.

stavros

> Am I the only one to stick with the std lib, read the docs and changelogs carefully

I work in healthcare. If I have a choice between "reading docs/changelogs carefully, implementing functions", and "adding an extra dependency", I'm taking the dependency every single time.

I don't want footguns in my code, I don't want code I have to write and test myself, and I don't want to have to become an expert in a domain before I can write something that serves my purpose.

For the datetime library, specifically, I'm switching to whenever for everything, because I've been bitten by conversions and naive/aware datetime confusion too many times.

jethkl

My hope is that a lib like this one or similar could rally mindshare and become integrated as the new standard, and adopted by the wider developer community. In near term, it comes down to trade-offs. I see no decision that works for all use cases. Dependencies introduce ticking time bombs, stdlibs should be correct and intuitive, but at least when not they are usually well tested and maintained, but when stdlib don't meet urgent production needs you have to do something.

Link to Tom Scott & Computerphile from 10y ago on tz madness. https://www.youtube.com/watch?v=-5wpm-gesOY

matsemann

It's basically what happened in Java. Everyone used jodatime, and they took great inspiration from that when making the new standard time api for java 8.

mark-r

Is there any part of the Python standard library written in Rust? I would see that as a big impediment to having Whenever adopted as standard.

barbazoo

I get where you’re coming from. There’s a price you pay though eventually. You’ll have to thoroughly vet all your dependencies for malicious code at some point. Otherwise how do you have any clue what you’re running?

globular-toast

> I work in healthcare. If I have a choice between "reading docs/changelogs carefully, implementing functions", and "adding an extra dependency", I'm taking the dependency every single time.

This kinda sums up the sorry state of software engineering. People can't even be bothered to read docs but will just blindly install a package just because someone was able to package it and upload it to PyPI.

Taking on a dependency does not relieve you of reading docs, but it also adds a further burden as you now need to trust the code. The stdlib is much more heavily tested and documented than any 3rd party library will be.

oefrha

The stdlib datetime module is in more of a sorry state than certain third party libraries and is full of footguns, as you, a read-the-docs-and-changelogs-carefully person, can surely tell from the myriad deprecations and warnings added over the years, not to mention the confusing params like tz and tzinfo. Heavily tested doesn’t mean shit when the semantics of the API is fundamentally flawed and can’t be updated.

crote

> Taking on a dependency does not relieve you of reading docs, but it also adds a further burden as you now need to trust the code. The stdlib is much more heavily tested and documented than any 3rd party library will be.

Sure, but the opposite applies as well. Sticking with the flawed stdlib means you are trusting that every single future developer is as careful in reading all the docs as you are - even when it's someone reviewing that random trivial-looking patch which is secretly hiding a massive footgun. A junior developer submitted a five-line patch touching datetime? Better schedule several hours for a proper analysis!

Or you can write your own wrapper code, of course. Which will almost certainly have worse documentation and testing than a popular third-party library.

External libraries aren't evil. When chosen properly, they relieve you of burdens. You shouldn't grab any random "leftpad"-like dependency like they are going out of fashion, but something as complicated as timezone handling is best left to code written by domain experts - which means using a library.

stavros

> The stdlib is much more heavily tested and documented than any 3rd party library will be.

You initially said you write your own code instead of using libraries, I replied to that, and now it's that you use the stdlib instead of libraries. I won't argue against shifting goalposts.

mr_mitm

> People can't even be bothered to read docs but will just blindly install a package just because someone was able to package it and upload it to PyPI.

That's a straw man argument. No one said "blindly". You can very well carefully consider the pros and cons of adding a dependency and arrive at the conclusion that it makes sense. Many PyPI packages are in the Debian stable repositories, you could use that as an additional barrier as well.

paulddraper

Did OP blindly install the package?

Or do it with sight?

EdwardDiego

There are so many footguns in the datetime lib.

That's why I use a Flake8 plugin to prohibit especially egregious footguns.

https://github.com/jkittner/flake8-ban-utcnow

raverbashing

Honestly yeah who in tarnation created that function and called it utcnow

These things are really frustrating

mark-r

Yeah, utcnow is completely broken. They should have fixed it when they created datetime.timezone.utc, but they didn't. The recommendation is to use datetime.datetime.now(datetime.timezone.utc) instead. utcnow should be deprecated and eventually removed.

sgarland

You are a sad minority, IME. I’m right there with you. I extended the uuid library to generate UUIDv7, based off of the RFC. It’s pretty easy to implement, as it turns out. Overruled, because “we don’t want to have to maintain additional code.” As if the ABI for bitshifts is going to change?!

ljm

There’s an out of sight, out of mind mentality with dependencies.

As long as there is a conscious decision to build or ‘buy’, it’s fine. I think some people can be a little too careless with adding dependencies though, not realising they can have an equal if not greater maintenance burden.

foolfoolz

this is a great idea if you want to slow down your project. most projects start with few rules and “best practices” like this. everyone is free to pull in dependencies as needed. because they are needed. but then once the project grows larger, those who have been around longer want to reverse course and gatekeep dependencies. but this is the opposite of what helped the project grow initially. and later contributors have a harder time making similar progress because they have to fight to add basic libraries. ensuring that efficiency per engineer goes down

johnfn

I think this is fairly unrealistic. Does all your datetime manipulation involve proper use of the fold parameter as indicated in the article?

mr_mitm

Are you saying you never pull in dependencies? Why stop there, why not re-implement the std lib as well? Surely there is a sensible middle ground: If you only need a small part of a dependency, consider implementing it. If you make heavy use of a dependency and want to benefit of years if not decades of dedicated developers testing and maturing its code, with a large community who has already stepped in all pitfalls you might step into and collectively encountered all the edge cases, just use the dependency.

xandrius

Creating from scratch also creates hidden debt, it's just moved onto yourself. Especially when working with dates and timezones.

brookst

I cannot imagine having the spare time to invest in building date/time foundations and maintaining them through changes to DST timing and country/time zone changes.

The only crazier idea I can think of is implementing character encoding conversions myself.

crazygringo

Seriously.

Nobody needs a package for "left-pad", which is the most infamous example.

But there are a lot of areas where it's not a good use of your time to reinvent the wheel. There's no moral virtue in writing everything yourself. And popular packages have gone through a more bug-finding and bug-fixing than your personal code probably ever will.

raverbashing

A library that goes "poof" when you need to upgrade it is also a hidden debt

0xCE0

There is also a distinction to be made between "technical" and "political" dependencies. Technical dependencies usually track some spec or common consensus, and political dependencies are e.g. timedate-library. Political dependencies are almost like business logic, because they have to track changing political decision made all over the world (or be local to some country).

Timedates are hard, and units may require even harder historical/present "political" tracking as how they are defined, and I would never want to maintain this kind of dependency: https://github.com/ryantenney/gnu-units/blob/master/units.da...

And what comes to timedate problems, I try to keep it simple if the project allows: store and operate with UTC timestamps everywhere and only temporarily convert to to local time (DSTs applied, if such) when displaying it in user-facing UI. This functionality/understanding can be locked into own 20-line microlibrary-dependency, which forces its responsible person to understand country's timezone and when e.g. DST changes and where/how/who decides DST changes and what APIs is used to get UTC time (and of course, its dependencies, e.g. NTP, and its dependencies. e.g. unperturbed ground-state hyperfine transition frequency of the caesium-133 atom, which result is then combined with the Pope Gregory XIII's Gregorian calendar, which is a type of solar calendar mixed with religious event for fixing the time, which which is then finally corrected by rewinding/forwarding "the clock" because its too bright or dark for the politicians).

xandrius

A service goes poof, a library either slowly deteriorates or breaks just as easily as a self-written one (if an underlying platform breaks it for some reason). The self-written one is maintained by 1 person, the other is used by 100+ people who could jump in a collaborate on its fixing.

I would still rather using a library for dates, a million times so.

scott_w

A member of staff who goes “poof” as the only one who understands your wrapper library that has a critical bug is a more common kind of hidden debt.

dmos62

Curious about examples of projects being killed by dependencies.

michaelt

While I've never seen a project killed by dependencies, I've certainly seen projects stuck on treadmill of constant dependency updates.

You know, they import 5 libraries, each of which imports 5 more libraries, each of which imports 5 more libraries, and suddenly they're buried in 'critical' updates because there's a denial-of-service bug in the date parser used by the yaml parser used by the configuration library used by the logging library used by the application.

ljm

Not killed IME but bloated and dragged down by tech debt.

E.g the JS project that uses the stdlib Date API, and pulls in moment.js, and also uses date-fns.

Or the one that pulls in bits and pieces of lodash, ramda, and other functional libraries.

And maybe it uses native fetch and axios depending on the current phase of the moon.

They don’t die but time is wasted in code review trying to understand if there is any kind of deliberate approach behind the scattershot application of packages with duplicated purposes.

(picking on JS is perhaps unfair but it’s probably the most egregious example of dependency hell)

MrJohz

The Date example is possibly an even better example of why dependencies that get hard problems right are so important. If Python's datetime library is bad, Date is truly terrible. Every time I have used it I have regretted it long term (including when using it in combination with date-fns in the hope that that makes it usable). And in the end, trying to keep things simple with Date has caused more technical debt than using a sensible dependency.

Some problems simply require using the right tools. They aren't necessarily hard, but they will be if you try to hammer a nail in with a screwdriver. The Date API, and to a certain extent Python's datetime module, are screwdrivers for a nail-shaped problem.

The rest of your example seem to have more to do with bad dependency practices than using dependencies in the first place. If you are going to include a dependency, think about it, consider whether it's worth it, document that decision, and then consistently use that dependency. Just because you've seen projects use dependencies poorly doesn't mean dependents are bad by themselves.

kelseydh

A big revelation for me in solving so much timezone insanity came from realising that timezones should be expressed as locations rather than zones.

Avoid general terms like "Pacific Standard Time" and stick to location-specific ones like: "Vancouver/Canada". The latter is how people expect their time to work, and correctly handles whatever quirky choices jurisdictions choose to do with their time.

throwaway2037

In my experience, all worthy date/time libraries use time zone IDs from the "tz database". Ref: https://en.wikipedia.org/wiki/Tz_database

Searching the list here: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

I cannot find an entry for "Pacific Standard Time" nor "Vancouver/Canada", but I can see: "America/Vancouver".

JimDabell

The rule of thumb is: Use UTC to record when things happened (e.g. logging), use local time + timezone name (e.g. `Europe/London`) to schedule things for the future (e.g. meetings).

Kwpolska

> available in Rust or pure Python.

Hard pass. The complexity of having to use binary packages or build things is not worth the performance benefit. The pure-Python version requires building from source and passing special flags, so it is not possible to specify it in requirements.txt.

stavros

That seems like an easy fix, they could release it as `whenever[pure]`. It would probably take less time to write up the issue than to write your comment.

Kwpolska

Extras only affect dependencies, you can’t have different codebases for them.

An issue was closed as not planned: https://github.com/ariebovenberg/whenever/issues/158

ariebovenberg

Author here. To summarize the long discussion in the issue:

1. I _love_ pure Python packages. Not everybody should be forced to use Rust. I want installing pure-Python to be as easy as possible

2. Having separate names on PyPi (with or without extras) creates confusion for libraries depending on whenever: should they depend on whenever-py or whenever-rust? If one “overwrites” the other, this adds confusion.

3. Most users expect to “pip install whenever” and start using the “fast” version

For me, points (3) and (2) weigh heavy enough to make (1) slightly more cumbersome.

But: Maybe I’ve missed something. Have a read in the above issue or add your 2 cents.

edit: formatting

OJFord

> The pure-Python version requires building from source and passing special flags, so it is not possible to specify it in requirements.txt.

You can put any flags in requirements.txt, including -r[equiring] another txt etc.

Your point may apply to modern pyproject.toml tooling though, or at least that it wouldn't be simply another entry in the dependencies array.

Kwpolska

The special flags are environment variables, you can’t pass that in requirements.txt: https://whenever.readthedocs.io/en/latest/faq.html#how-can-i...

BiteCode_dev

Ah, so you are not using pyQT, numpy, any database driver, pillow or anything using cryptography, then?

Kwpolska

For the libraries you listed, the benefits of using a native library are much larger, since they’re wrapping a well-known library that is known to be secure and fully-featured, or since the performance benefits are actually visible in any significant code snippet. But here, there is no Rust library to wrap, and I doubt the performance of a date-time library would have any effect on the performance of virtually all applications (maybe except for calendar apps).

karlicoss

datetime handling can absolutely be a hot spot, especially if you're parsing or formatting them. Even for relatively simple things like "parse a huge csv file with dates into dataclasses".

In particular, default implementation of datetime in cpython is a C module (with a fallback to pure python one) https://github.com/python/cpython/blob/main/Modules/_datetim...

Not saying it's necessarily justified in case of this library, but if they want to compete with stdlib datetime in terms of performance, some parts will need to be compiled.

qwertox

> If performance isn't your top priority, a pure Python version is available as well.

Then it would have been nice to see the benchmarks of the pure Python implementation as well. What if it's worse than arrow?

ariebovenberg

Author here. It's answered briefly in the FAQ

> In casual benchmarks, the pure-Python version is about 10x slower than the Rust version, making it 5x slower than the standard library but still (in general) faster than Pendulum and Arrow.

"(in general)" here since the speed compares differently per operation, while the Rust version is faster across the board. That said, there's no operation that is _significantly_ (or unnecessarily) slower than Arrow or Pendulum.

edit: I'm considering adding comparison to the pure Python version once I get the time for a more expanded "benchmarks" page in the docs

qwertox

Thank you. My apologies for not reading the FAQ. Also thank you for sharing your library.

ariebovenberg

No problem. After all, the readme does go from mentioning Pure Python directly to showing a benchmark graph where it's curiously absent

wodenokoto

Funny it doesn’t add comparison to date times in pandas, which is probably used to handle more dates than any of the others.

jiggunjer

Pandas uses stdlib or numpy for it seems.

vjerancrnjak

Does someone know when these performance issues matter? My understanding is that datetime is a shortlived object, you wouldn't want thousands of datetime objects all over the codebase.

Almost all of the time UTC is enough, if I need to filter/bucket/aggregate by some range, I can reach for datetime with tz for these filter/bucket/aggregate criteria, convert them to UTC and on continues `int` comparison.

I'd imagine all of the cases handled by Whenever are mostly when datetime is a long lived object, which I don't see a need for at all.

I use it purely for allowing tz input from client, convert to UTC immediately when it arrives, or, if I really need the tz, then save it separately, which is rare (one example is calendar, where tz should be stored, although probably not even next to every UTC but at the user level, another is workforce scheduling, where 8am-4pm or 8pm-4am can mean different things for different locations -- but this is no longer datetime, it's purely time in a timezone).

crazygringo

In my experience it's for calendar-related stuff. You need to store things permanently with the timezone, especially for recurring events. You don't want your scheduled lunch to move from 12 to 1 because it's DST.

And so anything server-related with calendars will be making tons of these conversions constantly. And you can't cache things long-term in UTC because the conversions of future events can change, when countries change DST etc.

vjerancrnjak

But lunch is 12 in time, not in date. You have to decide, with short lived datetime what the desired outcome is for today.

So you would not store that in UTC but just in time.

But yes, I’m ignoring the standard of calendar formats , maybe they are simpler .

I read through the article listing all the weirdness of other datetime libraries and I’d say many were covering cases where you behave that timezoned datetime is long lived .

One case even pointed out datetime construction with an impossible hour.

crazygringo

No, my weekly lunch is at 12 every 7 days across a variety of dates. The number of hours between each lunch changes due to DST.

atbpaca

I like that the type names are the same as in Java (java.time package). Great work!

skeledrew

I go for Arrow when I want anything beyond the basics. This looks pretty interesting, not really because of the greater coverage in edge cases, but because while it has a Rustified mode, a pure Python mode is also available. If I do use whenever, I don't have to worry about having something else or falling back to datetime if I want better datetime handling in a project on my phone, or in some other environment where the Rust toolchain is non-existent or problematic. Kudos.

mixmastamyk

Sounds like we need an industry/language-wide test suite to check these many date/time/calendar libraries against. Like the browser acid tests, though focused to baseline functionality only.

https://en.wikipedia.org/wiki/Acid3

I like this new lib (Thank You) but the name unfortunately implies the opposite of what it is. "Whenever" sounds like you don't care, but you'd only be using this if you did care! Also Shakira, haha. Hmm, pedantic is taken. Timely, precise, punctual, meticulous, ahorita, pronto, etc. I like that temporal name.

Finally, none of these links mention immutability, but it should be mentioned at the top.

mdaniel

Without the slightest sense of irony, I actually strongly suspect such a test suite would only be valid at one moment in time, since the timezone legislation is almost continuously in flux. That's why <https://www.iana.org/time-zones> and its friend <https://www.oracle.com/java/technologies/javase/tzupdater-re...> exist. As if to illustrate my point, the latest update was 2025-03-22, presumably nuking any such conformance test from Mar 21st

NeutralForest

In that case, you'd have unit tests that confirm behaviors like compatibility or failure of some operations between types and integrations tests which pull an up to date DB of rules and tests against that.

mixmastamyk

It would have to take the real world into account, no? Additionally it could test various timezone definition permutations without necessarily being dependent on a real one.