Exceptions and warnings

A value outside its domain raises ValueError, on both backends; the four exceptions below refine it, so one except ValueError catches everything parsing and conversion can raise. OverflowError escapes only for an integer too large for the backend’s machine integer. An argument of the wrong type usually raises TypeError (sometimes AttributeError); that is not guaranteed, so rely on a type checker to catch it.

Warnings

exception whenever.WheneverWarning[source]

Bases: UserWarning

Base class for all warnings emitted by the whenever library.

This can be used with Python’s standard warning filters to suppress or escalate all warnings emitted by whenever:

import warnings, whenever
warnings.filterwarnings("error", category=whenever.WheneverWarning)
exception whenever.PotentialDstBugWarning[source]

Bases: WheneverWarning

Base class for warnings about potential DST-related bugs in user code.

Not raised directly. Subclasses cover four distinct scenarios:

Catching or filtering this base class handles all four at once:

import warnings, whenever
warnings.filterwarnings("error", category=whenever.PotentialDstBugWarning)
exception whenever.DaysAssumed24HoursWarning[source]

Bases: PotentialDstBugWarning

Emitted when days are treated as exactly 24 hours, which may be wrong across a DST transition.

TimeDelta always represents exact time. Constructing one with days or weeks kwargs converts those units to nanoseconds using fixed 86400-second days. If you later add this delta to a ZonedDateTime on a day where clocks spring forward or fall back, the local time of the result will be off by the transition length (usually one hour).

When it can occur

Constructing a TimeDelta with days or weeks, passing them to Instant.add(), Instant.subtract(), or TimePatch.shift(), and the delta methods that read them as exact units, such as TimeDelta.in_units().

from whenever import TimeDelta, ZonedDateTime

# TimeDelta(days=1) is exactly 86 400 seconds — no DST awareness.
delta = TimeDelta(days=1)  # DaysAssumed24HoursWarning

# Adding it to a ZonedDateTime across a spring-forward night gives
# the wrong local time:
eve = ZonedDateTime(2025, 3, 29, 12, tz="Europe/Amsterdam")
eve + delta
# ZonedDateTime("2025-03-30 13:00:00+02:00[Europe/Amsterdam]")
# ^^ 13:00, not 12:00 — one hour lost to the DST transition

How to fix it

Use calendar-based arithmetic directly on the datetime to preserve local time across transitions:

eve.add(days=1)
# ZonedDateTime("2025-03-30 12:00:00+02:00[Europe/Amsterdam]")  ✓

To suppress when exact 24-hour arithmetic is genuinely intended, pass days_assumed_24h_ok=True (or use Python’s standard warning filters):

TimeDelta(days=1, days_assumed_24h_ok=True)
exception whenever.StaleOffsetWarning[source]

Bases: PotentialDstBugWarning

Emitted when an OffsetDateTime operation may preserve an offset that is stale relative to its source time zone.

Pass stale_offset_ok=True to accept this for one call. See OffsetDateTime for the stale-offset footgun, examples, and remediation.

exception whenever.NaiveArithmeticWarning[source]

Bases: PotentialDstBugWarning

Emitted when exact-time arithmetic is performed on a PlainDateTime without time zone context.

PlainDateTime carries no time zone information, so it can’t account for DST transitions. When you add or subtract exact time units (hours, minutes, seconds) or measure the exact difference between two PlainDateTime values, the computation treats every hour as equal. If a time zone transition falls in the interval, the result may be off by an hour or more.

When it can occur

Adding or subtracting exact units with add(), subtract(), +, or -; measuring exact units with -, difference(), since(), or until(); and a delta method whose relative_to is a PlainDateTime.

from whenever import PlainDateTime

# On 2023-10-29, Amsterdam clocks fall back at 3:00 AM.
# PlainDateTime has no knowledge of this.
d = PlainDateTime(2023, 10, 29, 1, 30)
d.add(hours=2)  # NaiveArithmeticWarning
# PlainDateTime("2023-10-29 03:30:00")
# ^^ only 1 real hour passed in Amsterdam (clocks went back)

# Also emitted for exact-unit differences:
d2 = PlainDateTime(2023, 10, 30, 1, 30)
d2 - d  # NaiveArithmeticWarning

How to fix it

Attach a time zone with assume_tz() first, then perform arithmetic on the resulting ZonedDateTime:

d.assume_tz("Europe/Amsterdam").add(hours=2)
# ZonedDateTime("2023-10-29 02:30:00+01:00[Europe/Amsterdam]")  ✓

To suppress when time zone context doesn’t apply (e.g. simulations, clock times not tied to a real-world time zone, or when you know no transitions occur in the interval), pass naive_arithmetic_ok=True (or use Python’s standard warning filters):

d.add(hours=2, naive_arithmetic_ok=True)
exception whenever.ImplicitDisambiguationWarning[source]

Bases: PotentialDstBugWarning

Emitted when a repeated or skipped local datetime is resolved without an explicit disambiguation policy.

Such local datetimes occur around time zone transitions and do not identify one unambiguous instant. Pass disambiguation= explicitly to document whether the compatible, earlier, later, or rejecting behavior is intended.

exception whenever.MonthCompositionWarning

Bases: WheneverWarning

Warn when itemized deltas with years or months are composed component-wise.

ItemizedDateDelta(months=1) + ItemizedDateDelta(months=1) is ItemizedDateDelta(months=2): the components add up, and the order the deltas would apply in is gone. With years or months that order matters, because they clamp at the end of the month. From January 31, one month and then another lands on March 28; two months at once land on March 31. Days, weeks, and exact units never clamp, and composing them doesn’t warn.

Composition is flagged rather than refused: see Footguns are flagged, not forbidden. To apply the deltas in turn, add each to the date or datetime. If the composed delta is what you want, pass month_composition_ok=True to add() or subtract().

exception whenever.PickleOffsetMismatchWarning[source]

Bases: WheneverWarning

The offset stored in a ZonedDateTime pickle no longer matches the time zone’s current rules. The instant is preserved; the local fields and offset are recomputed from the current rules. Not escapable per call: filter the category to reject a mismatch. See the pickling guide and ADR 0006.

exception whenever.WheneverDeprecationWarning[source]

Bases: WheneverWarning

Emitted when a deprecated feature of the whenever library is used.

This is a custom warning class (not a subclass of DeprecationWarning) so that deprecation warnings from this library are visible by default—unlike standard DeprecationWarning, which Python silences in application code.

Exceptions

exception whenever.RepeatedTime[source]

Bases: ValueError

A local time that is repeated in a time zone, e.g. because of DST.

See repeated local time.

exception whenever.SkippedTime[source]

Bases: ValueError

A local time that is skipped in a time zone, e.g. because of DST.

See skipped local time.

exception whenever.InvalidOffsetError[source]

Bases: ValueError

The offset in the input matches no offset the time zone applies to the written local time. Raised by the ISO and pattern parsers, the datetime constructor overload, and assume_tz() under offset_mismatch="raise".

exception whenever.TimeZoneNotFoundError[source]

Bases: ValueError

The string names no time zone: an unknown or malformed time zone ID, or a system time zone that cannot be resolved.