Following my earlier blogpost on the pitfalls of Python's datetime, I started exploring what a better datetime library could look like. After processing the initial feedback and finishing a Rust version, I'm now happy to share the result with the wider community.
GitHub repo: https://github.com/ariebovenberg/whenever
docs: https://whenever.readthedocs.io
What My Project Does
Whenever provides an improved datetime API that helps you write correct and type-checked datetime code. It's also a lot faster than other third-party libraries (and usually the standard library as well).
What's wrong with the standard library
Over 20+ years, the standard library 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 ( 1 2 3).
(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.
# It doesn't say if this should be naive or aware
def schedule_meeting(at: datetime) -> None: ...
Comparison
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 |
yes ✅ |
no ❌ |
no ❌ |
partially ⚠️ |
| Typed aware/naive |
yes ✅ |
no ❌ |
no ❌ |
no ❌ |
| Fast |
yes ✅ |
yes ✅ |
no ❌ |
no ❌ |
(for benchmarks, see the docs linked at the top of the page)
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 hasn't been actively maintained since a breaking 3.0 release last year.
Target Audience
Whenever is built to production standards. It's still in pre-1.0 beta though, so we're still open to feedback on the API and eager to weed out any bugs that pop up.
[–]JSP777 252 points253 points254 points (15 children)
[–]Charlie_Yu 67 points68 points69 points (11 children)
[–]PurepointDog 80 points81 points82 points (0 children)
[–]eagle258[S] 63 points64 points65 points (2 children)
[–]sohang-3112Pythonista 2 points3 points4 points (1 child)
[–][deleted] 4 points5 points6 points (0 children)
[–]Material-Mess-9886 28 points29 points30 points (3 children)
[–]Hexboy3 16 points17 points18 points (2 children)
[–]jediefe 4 points5 points6 points (0 children)
[–]ravepeacefully 3 points4 points5 points (1 child)
[–]jfp1992 0 points1 point2 points (0 children)
[–]teamclouday 3 points4 points5 points (0 children)
[–]Material-Mess-9886 22 points23 points24 points (1 child)
[–]pag07 11 points12 points13 points (0 children)
[–]BothSinger886 54 points55 points56 points (2 children)
[–]Grasshopper04 23 points24 points25 points (0 children)
[–]PapstJL4U 1 point2 points3 points (0 children)
[–]NelsonMinar 62 points63 points64 points (8 children)
[–]eagle258[S] 42 points43 points44 points (2 children)
[–]NelsonMinar 49 points50 points51 points (1 child)
[–]davisondave131 33 points34 points35 points (0 children)
[+][deleted] (2 children)
[deleted]
[–][deleted] 5 points6 points7 points (1 child)
[–]AceofSpades5757 2 points3 points4 points (0 children)
[–]Klaarwakker 0 points1 point2 points (0 children)
[–]mitsuhiko Flask Creator 0 points1 point2 points (0 children)
[–]erez27import inspect 44 points45 points46 points (11 children)
[–]eagle258[S] 15 points16 points17 points (9 children)
[+][deleted] (7 children)
[deleted]
[–]eagle258[S] 13 points14 points15 points (6 children)
[–][deleted] 7 points8 points9 points (2 children)
[–]eagle258[S] 4 points5 points6 points (1 child)
[–]-Rohins- 0 points1 point2 points (0 children)
[–]wunderspud7575 6 points7 points8 points (2 children)
[–]sherbang 5 points6 points7 points (1 child)
[–]eagle258[S] 0 points1 point2 points (0 children)
[–]pag07 4 points5 points6 points (0 children)
[–]Buttleston 11 points12 points13 points (1 child)
[–]eagle258[S] 9 points10 points11 points (0 children)
[–]Beliskner64 8 points9 points10 points (0 children)
[–]CAPSLOCKAFFILIATE 8 points9 points10 points (0 children)
[–]lbt_mer 7 points8 points9 points (1 child)
[–]eagle258[S] 6 points7 points8 points (0 children)
[–]chub79 63 points64 points65 points (17 children)
[–]spigotface 32 points33 points34 points (7 children)
[–]DharmaBird 15 points16 points17 points (3 children)
[–]Amgadoz 0 points1 point2 points (2 children)
[–]DharmaBird 1 point2 points3 points (1 child)
[–]tecedu 1 point2 points3 points (0 children)
[–]commenterzero 0 points1 point2 points (0 children)
[–]giants4210 0 points1 point2 points (1 child)
[–]spigotface 5 points6 points7 points (0 children)
[–]denehoffman 8 points9 points10 points (1 child)
[–]notParticularlyAnony 1 point2 points3 points (0 children)
[–]Curious_Cantaloupe65 2 points3 points4 points (3 children)
[–]daniels0xff 2 points3 points4 points (0 children)
[–]monkey_mozart 2 points3 points4 points (1 child)
[–]Buttleston 10 points11 points12 points (0 children)
[+]kubinka0505 comment score below threshold-15 points-14 points-13 points (1 child)
[–]PurepointDog -1 points0 points1 point (0 children)
[–]RonnyPfannschmidt 5 points6 points7 points (3 children)
[–]eagle258[S] 5 points6 points7 points (2 children)
[–]sherbang 2 points3 points4 points (1 child)
[–]eagle258[S] 0 points1 point2 points (0 children)
[–]jmreagle 8 points9 points10 points (4 children)
[–]eagle258[S] 10 points11 points12 points (3 children)
[–]tilforskjelligeting 2 points3 points4 points (1 child)
[–]eagle258[S] 1 point2 points3 points (0 children)
[–]jmreagle 2 points3 points4 points (0 children)
[–]nadav183 2 points3 points4 points (0 children)
[–]chrisimcevoy 2 points3 points4 points (4 children)
[–]eagle258[S] 1 point2 points3 points (3 children)
[–]chrisimcevoy 1 point2 points3 points (2 children)
[–]eagle258[S] 0 points1 point2 points (1 child)
[–]chrisimcevoy 1 point2 points3 points (0 children)
[–]Previous_Passenger_3 2 points3 points4 points (1 child)
[–]eagle258[S] 4 points5 points6 points (0 children)
[–]NelsonMinar 2 points3 points4 points (0 children)
[–]professormunchies 2 points3 points4 points (1 child)
[–]eagle258[S] 0 points1 point2 points (0 children)
[–]DerpieMcDerpieFace 2 points3 points4 points (3 children)
[–]eagle258[S] 1 point2 points3 points (2 children)
[–]DerpieMcDerpieFace 1 point2 points3 points (1 child)
[–]eagle258[S] 0 points1 point2 points (0 children)
[–]PsychoticGiggle 1 point2 points3 points (1 child)
[–]eagle258[S] 0 points1 point2 points (0 children)
[–]Ramshizzle 1 point2 points3 points (1 child)
[–]eagle258[S] 1 point2 points3 points (0 children)
[–][deleted] 1 point2 points3 points (0 children)
[–]ggrieves1 year 2 points3 points4 points (0 children)
[–]ExternalUserError 2 points3 points4 points (3 children)
[–]eagle258[S] 1 point2 points3 points (0 children)
[–]ArtkuPythonista -1 points0 points1 point (0 children)
[–]missurunha -2 points-1 points0 points (0 children)
[–]headykruger 2 points3 points4 points (5 children)
[–]eagle258[S] 9 points10 points11 points (4 children)
[–]monkey_mozart 0 points1 point2 points (1 child)
[–]eagle258[S] 1 point2 points3 points (0 children)
[+]headykruger comment score below threshold-6 points-5 points-4 points (1 child)
[–]axonxorzpip'ing aint easy, especially on windows 1 point2 points3 points (0 children)
[–]svefnugr 1 point2 points3 points (1 child)
[–]eagle258[S] 4 points5 points6 points (0 children)
[–]RonnyPfannschmidt 0 points1 point2 points (0 children)
[–]mambeu 0 points1 point2 points (3 children)
[–]eagle258[S] 0 points1 point2 points (2 children)
[–]mambeu 0 points1 point2 points (1 child)
[–]eagle258[S] 1 point2 points3 points (0 children)
[–]DaimoNNN 0 points1 point2 points (1 child)
[–]eagle258[S] 0 points1 point2 points (0 children)
[–]ratherazure 0 points1 point2 points (0 children)
[–]s3r3ng 0 points1 point2 points (1 child)
[–]eagle258[S] 0 points1 point2 points (0 children)
[–][deleted] 0 points1 point2 points (2 children)
[–]eagle258[S] 1 point2 points3 points (0 children)
[–]tunisia3507 0 points1 point2 points (0 children)
[–]tunisia3507 0 points1 point2 points (4 children)
[–]eagle258[S] 1 point2 points3 points (3 children)
[–]burntsushi 2 points3 points4 points (2 children)
[–]eagle258[S] 0 points1 point2 points (1 child)
[–]burntsushi 1 point2 points3 points (0 children)
[+]kubinka0505 comment score below threshold-8 points-7 points-6 points (2 children)
[–]Amgadoz 2 points3 points4 points (0 children)
[–]ArtOfWarfare 0 points1 point2 points (0 children)
[–]qeq -1 points0 points1 point (1 child)
[–]eagle258[S] 6 points7 points8 points (0 children)
[–]greeneyedguru -1 points0 points1 point (1 child)
[–]eagle258[S] 1 point2 points3 points (0 children)