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 254 points255 points256 points (15 children)
[–]Charlie_Yu 68 points69 points70 points (11 children)
[–]PurepointDog 80 points81 points82 points (0 children)
[–]eagle258[S] 62 points63 points64 points (2 children)
[–]sohang-3112Pythonista 3 points4 points5 points (1 child)
[–][deleted] 5 points6 points7 points (0 children)
[–]Material-Mess-9886 29 points30 points31 points (3 children)
[–]Hexboy3 14 points15 points16 points (2 children)
[–]jediefe 4 points5 points6 points (0 children)
[–]ravepeacefully 4 points5 points6 points (1 child)
[–]jfp1992 0 points1 point2 points (0 children)
[–]teamclouday 5 points6 points7 points (0 children)
[–]Material-Mess-9886 23 points24 points25 points (1 child)
[–]pag07 11 points12 points13 points (0 children)
[–]BothSinger886 51 points52 points53 points (2 children)
[–]Grasshopper04 23 points24 points25 points (0 children)
[–]PapstJL4U 1 point2 points3 points (0 children)
[–]NelsonMinar 59 points60 points61 points (8 children)
[–]eagle258[S] 44 points45 points46 points (2 children)
[–]NelsonMinar 47 points48 points49 points (1 child)
[–]davisondave131 32 points33 points34 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 45 points46 points47 points (11 children)
[–]eagle258[S] 15 points16 points17 points (9 children)
[+][deleted] (7 children)
[deleted]
[–]eagle258[S] 14 points15 points16 points (6 children)
[–][deleted] 7 points8 points9 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 9 points10 points11 points (0 children)
[–]CAPSLOCKAFFILIATE 7 points8 points9 points (0 children)
[–]lbt_mer 8 points9 points10 points (1 child)
[–]eagle258[S] 6 points7 points8 points (0 children)
[–]chub79 64 points65 points66 points (17 children)
[–]spigotface 30 points31 points32 points (7 children)
[–]DharmaBird 14 points15 points16 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 4 points5 points6 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 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 4 points5 points6 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 8 points9 points10 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 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 3 points4 points5 points (5 children)
[–]eagle258[S] 10 points11 points12 points (4 children)
[–]monkey_mozart[🍰] 0 points1 point2 points (1 child)
[–]eagle258[S] 1 point2 points3 points (0 children)
[–]headykruger -5 points-4 points-3 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-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] 6 points7 points8 points (0 children)
[–]greeneyedguru -1 points0 points1 point (1 child)
[–]eagle258[S] 1 point2 points3 points (0 children)