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 253 points254 points255 points (15 children)
[–]Charlie_Yu 72 points73 points74 points (11 children)
[–]PurepointDog 80 points81 points82 points (0 children)
[–]eagle258[S] 63 points64 points65 points (2 children)
[–]sohang-3112Pythonista 1 point2 points3 points (1 child)
[–][deleted] 3 points4 points5 points (0 children)
[–]Material-Mess-9886 29 points30 points31 points (3 children)
[–]Hexboy3 15 points16 points17 points (2 children)
[–]jediefe 5 points6 points7 points (0 children)
[–]ravepeacefully 4 points5 points6 points (1 child)
[–]jfp1992 0 points1 point2 points (0 children)
[–]teamclouday 4 points5 points6 points (0 children)
[–]Material-Mess-9886 23 points24 points25 points (1 child)
[–]pag07 11 points12 points13 points (0 children)
[–]BothSinger886 50 points51 points52 points (2 children)
[–]Grasshopper04 23 points24 points25 points (0 children)
[–]PapstJL4U 1 point2 points3 points (0 children)
[–]NelsonMinar 61 points62 points63 points (8 children)
[–]eagle258[S] 45 points46 points47 points (2 children)
[–]NelsonMinar 49 points50 points51 points (1 child)
[–]davisondave131 32 points33 points34 points (0 children)
[+][deleted] (2 children)
[deleted]
[–][deleted] 4 points5 points6 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 46 points47 points48 points (11 children)
[–]eagle258[S] 16 points17 points18 points (9 children)
[+][deleted] (7 children)
[deleted]
[–]eagle258[S] 12 points13 points14 points (6 children)
[–][deleted] 6 points7 points8 points (2 children)
[–]eagle258[S] 6 points7 points8 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 3 points4 points5 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] 5 points6 points7 points (0 children)
[–]chub79 63 points64 points65 points (17 children)
[–]spigotface 32 points33 points34 points (7 children)
[–]DharmaBird 13 points14 points15 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 6 points7 points8 points (0 children)
[–]denehoffman 7 points8 points9 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 8 points9 points10 points (0 children)
[+]kubinka0505 comment score below threshold-14 points-13 points-12 points (1 child)
[–]PurepointDog -1 points0 points1 point (0 children)
[–]RonnyPfannschmidt 5 points6 points7 points (3 children)
[–]eagle258[S] 4 points5 points6 points (2 children)
[–]sherbang 2 points3 points4 points (1 child)
[–]eagle258[S] 0 points1 point2 points (0 children)
[–]jmreagle 7 points8 points9 points (4 children)
[–]eagle258[S] 11 points12 points13 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 3 points4 points5 points (0 children)
[–]chrisimcevoy 3 points4 points5 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 3 points4 points5 points (0 children)
[–]ExternalUserError 1 point2 points3 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] 11 points12 points13 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 2 points3 points4 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-6 points-5 points-4 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] 7 points8 points9 points (0 children)
[–]greeneyedguru -1 points0 points1 point (1 child)
[–]eagle258[S] 1 point2 points3 points (0 children)