Package: whenever
Strict, predictable, and typed datetimesDo you cross your fingers every time you work with datetimes,
hoping that you didn’t mix naive and aware?
or that you converted to UTC everywhere?
or that you avoided the manypitfalls of the standard library?
There’s no way to be sure…✨ Until now! ✨Wheneveris a datetime library designed from the ground up to enforce correctness.
Mistakes become red squiggles in your IDE, instead of bugs in production.BenefitsDistinct classes with well-defined behaviorFixes datetime pitfalls thatArrow and Pendulum don’t addressEnforce correctness without runtime checksBased onfamiliar concepts from other languages. Doesn’t reinvent the wheelSimple and obvious. No frills or surprisesThoroughly documentedand testedNo third-party dependenciesQuickstart>>>fromwheneverimport(...# Explicit types for different use cases...UTCDateTime,# -> Great for codebases that normalize to UTC...OffsetDateTime,# -> Localized times without ambiguities...ZonedDateTime,# -> Full-featured IANA timezone support...LocalDateTime,# -> In the local system timezone...NaiveDateTime,# -> Detached from any timezone......hours,days,minutes# aliases for timedelta(hours=...) etc....)>>>py311_release=UTCDateTime(2022,10,24,hour=17)UTCDateTime(2022-10-2417:00:00Z)>>>pycon23_started=OffsetDateTime(2023,4,21,hour=9,offset=hours(-6))OffsetDateTime(2023-04-2109:00:00-06:00)# Simple, explicit conversions>>>py311_in_paris=py311_release.as_zoned("Europe/Paris")ZonedDateTime(2022-10-2419:00:00+02:00[Europe/Paris])>>>pycon23_started.as_local()LocalDateTime(2023-04-2111:00:00-04:00)# system timezone in NYC here# Comparison and equality across aware types>>>pycon23_started<py311_releaseFalse>>>py311_release==py311_release.as_zoned("America/Los_Angeles")True# DST-aware addition/subtraction>>>py311_in_paris+days(7)ZonedDateTime(2022-10-3118:00:00+01:00[Europe/Paris])# Naive type that can't accidentally be mixed with aware types>>>simulation_start=NaiveDateTime(1950,1,1,hour=9)>>># Mistakes caught by typechecker:>>>py311_release-simulation_start>>>simulation_start==pycon23_started# Lossless round-trip to/from text (useful for JSON/serialization)>>>py311_release.canonical_str()'2022-10-24T17:00:00Z'>>>ZonedDateTime.from_canonical_str('2022-10-24T19:00:00+02:00[Europe/Paris]')ZonedDateTime(2022-10-2419:00:00+02:00[Europe/Paris])# Conversion to/from common formats>>>py311_release.rfc2822()# also: from_rfc2822()"Mon, 24 Oct 2022 17:00:00 GMT">>>pycon23_started.rfc3339()# also: from_rfc3339()"2023-04-21T09:00:00-06:00"# Basic parsing>>>OffsetDateTime.strptime("2022-10-24+02:00","%Y-%m-%d%z")OffsetDateTime(2022-10-2400:00:00+02:00)# If you must: you can access the underlying datetime object>>>pycon23_started.py.ctime()'Fri Apr 21 09:00:00 2023'Read more in thefull overvieworAPI reference.Why not…?The standard libraryThe standard library is full of quirks and pitfalls.
To summarize the detailedblog post:Incompatible concepts of naive and aware are squeezed into one classOperations ignore Daylight Saving Time (DST)The meaning of “naive” is inconsistent (UTC, local, or unspecified?)Non-existent datetimes pass silently, then wreak havoc laterIt guesses in the face of ambiguityFalse negatives on equality of ambiguous times between timezonesFalse positives on equality of ambiguous times within the same timezonedatetimeinherits fromdate, but behaves inconsistentlydatetime.timezoneisn’t a timezone.ZoneInfois.The local timezone is DST-unawarePendulumPendulum is full-featured datetime library, but it’s
hamstrung by the decision to inherit from the standard librarydatetime.
This means it inherits most of the pitfalls mentioned above,
with the notable exception of DST-aware addition/subtraction.ArrowArrow is probably the most historically popular datetime library.
Pendulum did a good write-up ofthe issues with Arrow.
It addresses fewer of datetime’s pitfalls than Pendulum.DateTypeDateType mostly fixes the issue of mixing naive and aware datetimes,
and datetime/date inheritance during type-checking,
but doesn’t address the other pitfalls.
The type-checker-only approach also means that it doesn’t enforce correctness at runtime,
and it requires developers to be knowledgeable about
how the ‘type checking reality’ differs from the ‘runtime reality’.python-dateutilDateutil attempts to solve some of the issues with the standard library.
However, it onlyaddsfunctionality to work around the issues,
instead ofremovingthe pitfalls themselves.
This still puts the burden on the developer to know about the issues,
and to use the correct functions to avoid them.
Without removing the pitfalls, it’s still very likely to make mistakes.MayaIt’s unmaintained, but does have an interesting approach.
By enforcing UTC, it bypasses a lot of issues with the standard library.
To do so, it sacrifices the ability to represent offset, zoned, and local datetimes.
So in order to perform any timezone-aware operations, you need to convert
to the standard librarydatetimefirst, which reintroduces the issues.HeliclockterThis library is a lot more explicit about the different types of datetimes,
addressing issue of naive/aware mixing with UTC, local, and zoned datetime subclasses.
It doesn’t address the other datetime pitfalls though.FAQsWhy 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.Why not inherit from datetime?Not only would this keep most of the issues with the standard library,
it would result in brittle code: many popular libraries expectdatetimeexactly,
anddon’t workwith subclasses.What is the performance impact?Because whenever wraps the standard library, head-to-head performance will always be slightly slower.
However, becausewheneverremoves the need for many runtime checks,
it may result in a net performance gain in real-world applications.Why not a C or Rust extension?It actually did start out as a Rust extension. But since the wrapping code
is so simple, it didn’t make much performance difference.
Since it did make the code a lot more complex, a simple pure-Python implementation
was preferred.
If more involved operations are needed in the future, we can reconsider.Is this production-ready?The core functionality is complete and stable and the goal is to reach 1.0 soon.
The API may change slightly 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!Versioning and compatibility policyWheneverfollows semantic versioning.
Until the 1.0 version, the API may change with minor releases.
Breaking changes will be avoided as much as possible,
and meticulously explained in the changelog.
Since the API is fully typed, your typechecker and/or IDE
will help you adjust to any API changes.AcknowledgementsThis project is inspired by the following projects. Check them out!Noda TimeTemporalChronoContributingContributions are welcome! Please open an issue or a pull request.⚠️Note: big changes should be discussed in an issue first.
This is to avoid wasted effort if the change isn’t a good fit for the project.⚠️Note: Some tests are skipped on Windows.
These tests use unix-specific features to set the timezone for the current process.
As a result, Windows isn’t able to run certain tests that rely on the system timezone.
It appears thatthis functionality is not available on Windows.Setting up a development environmentYou’ll needpoetryinstalled.
An example of setting up things up:poetryinstall# To run the tests with the current Python versionpytest# if you want to build the docspipinstall-rdocs/requirements.txt# Various checksmypysrc/tests/flake8src/tests/# autoformattingblacksrc/tests/isortsrc/tests/# To run the tests with all supported Python versions
# Alternatively, let the github actions on the PR do it for youpipinstalltoxtox-pauto