Design philosophy

This page describes the guiding principles behind whenever’s API. For concrete questions, see the FAQ.

Separate types for separate meanings

If two concepts carry different semantics, they get different types—even when they look similar on the surface. For example, a datetime with a timezone (ZonedDateTime) and one with a fixed offset (OffsetDateTime) both represent a moment in time with a local clock reading, but only the former can track DST transitions. Encoding this distinction in the type system makes bugs that would otherwise surface at runtime visible at development time.

This principle also extends to deltas: an exact duration (TimeDelta), a bag of calendar units (ItemizedDateDelta), and a mixed bag (ItemizedDelta) each have different arithmetic rules. Keeping them as separate types prevents mixing operations that don’t make sense together.

Footguns are flagged, not forbidden

Some operations are potential footguns—but not always wrong. For example, doing arithmetic on a PlainDateTime can’t account for DST, but may be acceptable if the user knows DST isn’t relevant for their use case, or accepts the possibility of an incorrect result some of the time.

Outright forbidding these operations would push users toward workarounds that would obscure their intention. Whenever allows them but emits a warning, which can then explicitly and selectively be silenced.

Operators only where mathematically intuitive

Operators like +, -, *, and / are only defined when they obey the mathematical properties you’d expect—associativity, reversibility, and so on.

For instance, a + (b + c) == (a + b) + c doesn’t hold when b or c involve months (because months have variable lengths). Instead of silently breaking those expectations, whenever omits the operator and provides explicit methods (add(), subtract()) that don’t carry the same mathematical connotations.

Similarly, the - operator between two datetimes always returns a TimeDelta (an exact duration), because that’s the only type where subtraction is always reversible. For calendar-unit differences, use since() / until().

No system timezone by default

Many datetime libraries silently use the system timezone as a default, but this couples your code to the machine’s configuration—a common source of surprises, especially in servers and containers where the system timezone is often UTC or undefined. In whenever, the system timezone is never used implicitly; you must opt in with a dedicated method (e.g. to_system_tz(), assume_system_tz()) so the dependency is visible in the code.