Handling warnings

whenever emits warnings when operations may produce incorrect results, for example due to DST transitions, missing context, or component-wise composition of months. This is intentional: the operations are classic “footguns”, but forbidding them entirely would be too strict. Warnings are an ideal mechanism to ensure the potential issues don’t pass unnoticed.

All whenever warnings are subclasses of WheneverWarning, which is itself a subclass of Python’s built-in UserWarning. DST-related warnings are grouped under PotentialDstBugWarning. They fit into Python’s standard warnings infrastructure fully, giving you several levels of control.

UserWarning (stdlib)
└── WheneverWarning
    ├── MonthCompositionWarning
    ├── PickleOffsetMismatchWarning
    ├── PotentialDstBugWarning
    │   ├── DaysAssumed24HoursWarning
    │   ├── ImplicitDisambiguationWarning
    │   ├── NaiveArithmeticWarning
    │   └── StaleOffsetWarning
    └── WheneverDeprecationWarning

Two warnings have no class of their own and emit WheneverWarning itself: a pattern with a 12-hour clock (i or ii) and no AM/PM specifier, and a conversion that loses data, such as Date() given a datetime or a constructor given a pandas object. Filter them by message if either case needs separate handling.

Turn warnings into errors

For application code, turn whenever’s warnings into exceptions as early as possible — typically in your module’s setup or at the top of your application entry point:

import warnings
import whenever

warnings.filterwarnings("error", category=whenever.WheneverWarning)

If you only want to target DST-related warnings:

warnings.filterwarnings("error", category=whenever.PotentialDstBugWarning)

Any code that triggers a matching warning now raises an exception immediately, forcing you (or your CI) to address it. This is the same principle as PYTHONWARNINGS=error but scoped to whenever’s warning hierarchy only.

To target a specific warning type instead:

# Only error on arithmetic that ignores time zones (PlainDateTime):
warnings.filterwarnings("error", category=whenever.NaiveArithmeticWarning)

# Only error on potentially stale offset operations (OffsetDateTime):
warnings.filterwarnings("error", category=whenever.StaleOffsetWarning)

# Require an explicit policy for a repeated or skipped local time:
warnings.filterwarnings("error", category=whenever.ImplicitDisambiguationWarning)

In pytest

When running tests, it’s highly recommended to turn whenever warnings into errors so tests catch potential issues. Add this to your pytest.ini (or the [tool.pytest.ini_options] table in pyproject.toml):

[pytest]
filterwarnings =
    error::whenever.WheneverWarning

Or to target only one module of your project (leaving third-party libraries unaffected):

[pytest]
filterwarnings =
    error::whenever.WheneverWarning:mymodule.*

Command-line filter not supported

Unfortunately, passing PYTHONWARNINGS=error::whenever.WheneverWarning on the command line does not work, due to a limitation in CPython: the command-line filter only accepts built-in warning classes by name, not third-party ones. Use pytest.ini, pyproject.toml, or a call to warnings.filterwarnings() in your code instead.

In a specific module

You can also apply a filter at the top of a module, so it applies to all code in that module without touching other modules:

# mymodule/scheduling.py
import warnings
import whenever

warnings.filterwarnings(
    "error",
    category=whenever.WheneverWarning,
    module=r"mymodule\.scheduling"  # or re.escape(__name__)
)

Suppress specific calls

Sometimes an operation is deliberately imprecise — and that’s fine, as long as the decision is conscious and documented. Each warning that a single call can justify has a call-local escape: a boolean keyword ending in _ok that suppresses it for that one call, and is named in the warning’s message.

Keyword argument

Suppresses

Used on

days_assumed_24h_ok=True

DaysAssumed24HoursWarning

the TimeDelta constructor and methods, Instant add/subtract, TimePatch.shift()

stale_offset_ok=True

StaleOffsetWarning

OffsetDateTime add/subtract, since/until, now(), replace(), replace_date(), replace_time(), round(), start_of(), end_of(); the delta methods total() and in_units() with an OffsetDateTime as relative_to, except on an ItemizedDateDelta, which reads only the date

naive_arithmetic_ok=True

NaiveArithmeticWarning

PlainDateTime methods; the delta methods total() and in_units() with a PlainDateTime as relative_to, except on an ItemizedDateDelta, which reads only the date

month_composition_ok=True

MonthCompositionWarning

ItemizedDelta and ItemizedDateDelta add/subtract

disambiguation= (a policy, not a flag)

ImplicitDisambiguationWarning

ZonedDateTime construction, parse_iso(), parse(), replace(), replace_date(), replace_time(), add/subtract; assume_tz() on PlainDateTime and OffsetDateTime

none: filter the category

PickleOffsetMismatchWarning

pickle.loads() of a ZonedDateTime

none: fix the pattern

the 12-hour WheneverWarning

format() and parse(): add a/aa, or use H/HH

none: convert explicitly

the lossy-subclass WheneverWarning

constructors given a pandas or pendulum object, or Date() given a datetime

For example:

from whenever import PlainDateTime

# Naive arithmetic is acceptable here because <insert reason>
next_departure = scheduled.add(hours=1, naive_arithmetic_ok=True)

The keyword argument documents the decision at the call site while keeping the suppression limited to exactly one operation.

For the distinction between observed and permanently fixed offsets, see OffsetDateTime.

Operators

The + and - operators always emit warnings when applicable, because operators cannot accept keyword arguments. Use the method equivalents instead:

  • dt + delta → dt.add(delta, ...)

  • dt - delta → dt.subtract(delta, ...)

  • dt_a - dt_b → dt_a.difference(dt_b) (for PlainDateTime, pass naive_arithmetic_ok=True)

Composing itemized deltas with + or - emits MonthCompositionWarning when either operand has years or months. add() and subtract() take month_composition_ok=True instead.

Alternatively, suppress operator warnings with Python’s standard warnings.filterwarnings().

Using Python’s warnings infrastructure

Since whenever warnings are standard Python warnings, you can also suppress them with warnings.catch_warnings:

import warnings
import whenever

with warnings.catch_warnings():
    warnings.simplefilter("ignore", whenever.StaleOffsetWarning)
    # ... all stale-offset warnings suppressed inside this block

This is useful when you want to blanket-suppress warnings for a block of code or for operators (which can’t take keyword arguments).

Limitation in concurrent code

By default, warnings.catch_warnings is not context-safe: in concurrent code (threads or async tasks) the suppression filter may leak to other contexts, or other contexts may interfere with yours. This is a known CPython limitation addressed by the PYTHON_CONTEXT_AWARE_WARNINGS flag introduced in Python 3.14. The flag is on by default only in free-threaded builds.

The call-local escapes described above don’t have this limitation — they suppress the warning for exactly one call, regardless of concurrency.

Exploratory use and scripts

When hacking around or writing a quick script, you may simply want to silence all whenever warnings globally and move on:

import warnings
import whenever

warnings.filterwarnings("ignore", category=whenever.WheneverWarning)

This is fine for exploration. If you later promote the code to production, revisit the suppressed warnings and decide for each one whether to fix the underlying issue or suppress it explicitly with the appropriate keyword argument.

Choosing the right approach

Situation

Recommended approach

Application code

filterwarnings("error", ...) at startup

CI / test suite

filterwarnings = error::whenever.WheneverWarning in pytest.ini

One intentional imprecision

Call-local escape (e.g. naive_arithmetic_ok=True) + a comment

Suppress operator warnings

warnings.catch_warnings() block (thread-safe only with Python 3.14’s context_aware_warnings, on by default only in free-threaded builds)

Entire module intentionally imprecise

filterwarnings("ignore", ..., module=r"mymodule\.*")

Exploratory scripts

filterwarnings("ignore", ...) globally